This document captures the design rationale, architecture decisions, and phased deployment strategy for this project. It was written during the initial planning phase and is preserved here as reference for understanding why specific choices were made, particularly around Android compatibility and secure networking.
Raspberry Pi + CouchDB + Tailscale + Trusted HTTPS + Android-safe rollout
Set up a self-hosted Obsidian sync system using:
- Raspberry Pi as the always-on server
- Dockerized CouchDB as the LiveSync backend
- Tailscale for private connectivity
- a trusted HTTPS endpoint on the Pi’s stable DNS name
- Obsidian Self-hosted LiveSync plugin
- plugin setup wizard / setup URI
- Request API fallback if Android or CORS issues appear
This design maximises the chances of Android working reliably, based on the issues and documentation reviewed.
A plain Pi + CouchDB setup is not enough for Android.
Key findings:
-
Android cannot reliably use a plain
http://endpoint. -
Android also cannot reliably use a self-signed certificate.
-
A trusted HTTPS endpoint is required.
-
Even with trusted HTTPS, there can still be issues related to:
- CORS
- proxy configuration
- redirects
- mobile/WebView or local DB behaviour
-
The plugin’s own docs recommend:
- setup wizard / setup URI
- Use Request API if needed to work around CORS/fetch problems
Trusted HTTPS is necessary but not sufficient.
The design must reduce risk across all of these layers:
- transport security
- DNS consistency
- proxy simplicity
- CORS correctness
- careful phased rollout
- Android-first risk mitigation
- backups before trusting production notes
This project uses:
- Pi
- Docker CouchDB
- Tailscale
- trusted HTTPS endpoint on a stable DNS name
- simple routing
- no public internet exposure
- desktop-first test vault rollout
- Android second
- Request API only if needed
Obsidian Desktop / Android
|
| HTTPS
v
Trusted stable DNS endpoint
(on Pi, reachable over Tailscale)
|
v
Reverse proxy / HTTPS termination
|
v
CouchDB container on Raspberry Pi
|
v
Persistent Docker volume / SSD storage
- Do not expose CouchDB directly to the public internet
- Do not use self-signed certificates
- Do not use plain HTTP for Android
- Keep routing simple
- Avoid subpath routing if possible
- Use one clean hostname for the sync endpoint
- Keep the first version boring and predictable
-
Reliable sync between:
- Android phone
- Windows 11 laptops
- Ubuntu laptops
-
Always-on backend on Raspberry Pi
-
Secure private access
-
Trusted HTTPS endpoint for Android compatibility
-
Reproducible setup using setup URI
-
Backed-up server data
- Minimal operational complexity
- Easy onboarding of additional devices
- Easy rollback if sync issues appear
- Clear validation path before using real vault
Not in scope for v1:
- public internet access without Tailscale
- complex public reverse proxy chains
- Cloudflare in front of sync endpoint
- domain-wide routing experiments
- multiple reverse proxies
- subdirectory-based endpoint mapping
- migrating the real vault before validation
- mixing LiveSync with another sync method on the same vault
Android requires:
- trusted HTTPS
- no self-signed cert
- no plain HTTP
Even with HTTPS, sync may fail if:
- proxy rewrites headers incorrectly
- proxy handles CORS instead of CouchDB
- redirects are introduced
- host forwarding is wrong
Android may still show:
- “Failed to fetch”
- incomplete sync
- intermittent failures
- local storage / IndexedDB style issues
Like any sync system:
- bad config can produce conflicts
- plugin bugs or restore bugs may surface
- first sync on a real vault is risky without testing
This is a power-user setup, not a consumer appliance setup.
- Pi available and stable
- Docker already installed
- Tailscale already installed
- storage path decided
- backup path decided
- test vault ready
- confirm Pi hostname
- confirm Tailscale connectivity from all client devices
- confirm Pi has enough storage
- prefer SSD over SD card if possible
- create a dedicated project folder, e.g.:
/srv/obsidian-livesync- Pi is reachable over Tailscale
- Docker works
- stable DNS name is known
- backup target exists
Deploy a clean CouchDB backend on the Pi.
- create
docker-compose.yml - create persistent storage volume
- use strong admin credentials
- start CouchDB container
- validate container health locally
/srv/obsidian-livesync/
docker-compose.yml
data/
backups/
Note: This was the initial planning sketch. The actual
docker-compose.ymlin the repo root includes additional security hardening, health checks, resource limits, and environment variable support.
version: "3.8"
services:
couchdb:
image: couchdb:3
container_name: obsidian-couchdb
restart: unless-stopped
environment:
COUCHDB_USER: obsidian_admin
COUCHDB_PASSWORD: change-this-to-a-long-random-password
volumes:
- ./data:/opt/couchdb/data
ports:
- "5984:5984"docker ps- local access to CouchDB
- persistent storage mounted
- restart test passes
- CouchDB is running
- local access works
- data survives restart
Provide a trusted HTTPS endpoint suitable for Android.
This is mandatory for Android compatibility.
- stable DNS name
- trusted certificate
- simple route
- no redirect loops
- no subpath complexity if avoidable
Use a dedicated hostname for sync, for example:
https://obsidian-sync.<stable-domain-or-tailnet-name>
or equivalent stable DNS name that resolves consistently for all devices.
- no self-signed cert
- no raw
http:// - avoid certificate mismatch
- avoid connecting via bare IP if certificate is for hostname
- keep endpoint shape stable from day one
From desktop and Android:
- open the HTTPS URL in browser
- verify no cert warning
- verify certificate is trusted
- verify no redirect loop
- Android browser can open endpoint over HTTPS
- no cert warnings
- stable DNS name confirmed
Make the path between client and CouchDB predictable.
- keep proxy logic minimal
- do not add unnecessary layers
- no Cloudflare in front for v1
- no subpath routing if avoidable
- avoid path rewriting tricks
Important:
-
CouchDB should handle CORS
-
reverse proxy should not become the CORS authority
-
preserve:
HostX-Forwarded-For
-
avoid mishandling
OPTIONS
Use normal mode first if setup is clean.
Enable Request API if:
- Android gets “Failed to fetch”
- CORS errors appear
- behaviour differs between browser and plugin
- desktop works but Android does not
- HTTPS endpoint works without browser issues
- no redirect loops
- path is stable and simple
Prove the stack works on desktop before Android.
- create a small throwaway Obsidian vault
- install Self-hosted LiveSync plugin on one desktop
- use plugin setup wizard
- connect to the trusted HTTPS endpoint
- complete initial sync
- do not use the real vault yet
- do not configure multiple devices yet
- do not enable additional sync tools on this vault
- create a note
- edit a note
- rename a note
- create nested folders
- move notes between folders
- attach an image/file
- restart Obsidian and verify state
- restart Pi and verify sync still works
- desktop sync is stable
- no unexplained errors
- nested folders and attachments behave correctly
Use setup URI to reduce configuration errors on additional devices.
- once desktop is stable, generate the plugin setup URI
- store it securely
- document how to reuse it for new devices
- do not hand-configure every device from scratch unless necessary
- use the same canonical configuration
- setup URI available
- setup can be repeated consistently
Validate Android behaviour before using real notes.
- install Obsidian on Android
- open the same small test vault pattern locally
- install LiveSync plugin
- configure using setup URI
- connect to trusted HTTPS endpoint
- test initial sync
- sync existing notes from desktop
- create a note on Android and verify desktop receives it
- edit a note on Android and verify desktop receives it
- create nested folder and note
- attach a small file
- close and reopen app
- test over Wi-Fi
- test over mobile data if relevant
- test after phone sleep / wake
- test after Pi restart
If Android fails:
- verify endpoint opens in Android browser
- confirm cert is trusted
- confirm hostname matches certificate
- check proxy / redirects
- check CouchDB CORS config
- enable Request API
- retest
- inspect logs
- Android can sync test vault reliably
- no repeated fetch failures
- no missing subfolders or partially restored vault state
Add the rest of your laptops after desktop + Android are proven.
- onboard one additional device at a time
- use setup URI
- test basic sync after each addition
- no mass onboarding
- validate each device before adding the next
- avoid simultaneous heavy editing during rollout
- all intended devices connected
- all pass the basic sync validation set
Move from test vault to production vault safely.
All earlier phases must be green.
- back up current real vault
- back up Pi-side CouchDB data
- create a restore point
- onboard real vault through the validated workflow
- monitor carefully for several days
- do not migrate without verified backups
- do not edit the same note on multiple devices during the first few days
- do not add unrelated plugin experiments at the same time
- production vault syncs correctly across devices
- no unexplained missing files
- no repeated Android failures
- restore plan tested or at least documented
- Pi stable
- Docker persistent
- CouchDB restarts cleanly
- storage persists
- Tailscale connectivity works from all devices
- stable DNS name chosen
- HTTPS certificate trusted
- no redirect loop
- no certificate mismatch
- initial sync works
- note create/edit/delete works
- folders sync correctly
- attachments sync correctly
- restart survives
- reconnection after Pi restart works
- endpoint opens in browser
- no cert warning
- initial sync completes
- note create/edit/delete works
- folders sync correctly
- attachments sync correctly
- sleep/wake does not break sync
- app restart survives
- no repeated fetch failure
- Request API tested if needed
- backups configured
- restore procedure documented
- setup URI stored securely
- real vault backup taken before migration
Sync is not backup.
- CouchDB data directory / Docker volume
- exported or copied Obsidian vault
- important setup details:
- hostname
- credentials
- setup URI
- encryption keys/secrets if applicable
- nightly backup of
/srv/obsidian-livesync - retain several historical copies
- optional copy to another machine or disk
- tarball backup
- rsync to another host
- snapshot if storage supports it
- backups run automatically
- at least one restore drill is documented
- critical secrets are stored securely
- strong CouchDB admin password
- trusted HTTPS only
- no public raw port exposure
- no router port-forward to CouchDB
- Tailscale for private access
- minimal proxy surface
- principle of least exposure
- self-signed certificates
- plain HTTP
- public unauthenticated CouchDB
- exposing port 5984 directly to the open internet
Possible causes:
- CORS
- Request API needed
- redirect/proxy issue
- plugin-side fetch behaviour
- local mobile DB state issue
Actions:
- enable Request API
- verify no redirect chain
- confirm CouchDB handles CORS
- inspect logs
- retry with clean test vault
Possible causes:
- Android HTTPS/cert trust edge
- plugin fetch path issue
- mobile storage state issue
Actions:
- re-check hostname and cert match
- enable Request API
- re-test on small clean vault
- compare logs between desktop and phone
Possible causes:
- onboarding/restore issue
- interrupted first sync
- plugin state problem
Actions:
- stop rollout
- discard broken test vault
- retry from clean state
- confirm completion before further testing
Possible causes:
- mobile local DB problems
- network instability
- proxy redirect issue
- server restart handling issue
Actions:
- test with one desktop + Android only
- reduce variables
- inspect container logs
- inspect plugin logs
- validate after restart cycle
Use trusted HTTPS from day one.
Reason: Android requires it.
Use stable DNS hostname, not ad hoc IP-based access.
Reason: certificate trust and consistency.
Use desktop-first rollout.
Reason: reduces debugging complexity.
Use small test vault before real vault.
Reason: data safety.
Use setup wizard / setup URI.
Reason: fewer config mistakes.
Use Request API only if needed, not blindly from the start.
Reason: keep setup simple, but have a documented fallback ready.
Do not stack Cloudflare or extra public proxy layers in v1.
Reason: reduces redirect and CORS complexity.
This project is done when:
- CouchDB runs reliably on the Pi
- sync endpoint is reachable on a trusted HTTPS stable DNS name
- desktop test vault works reliably
- Android test vault works reliably
- Request API fallback path is understood and tested if required
- additional laptops can be onboarded using setup URI
- backups are configured
- real vault migration completes without data loss
- no major recurring sync issues appear during the monitoring period
- Create project folder on Pi
- Deploy CouchDB container
- Validate local CouchDB health
- Create the trusted HTTPS endpoint on stable DNS name
- Verify Android browser trust
- Create desktop test vault
- Install LiveSync and use setup wizard
- Validate desktop sync
- Generate setup URI
- Add Android test vault
- Enable Request API only if needed
- Roll out to remaining devices
- Migrate real vault only after successful validation
The safest path is:
boring infrastructure, trusted HTTPS, simple routing, desktop-first validation, Android second, real vault last.
That gives you the best chance of making LiveSync on Android work without turning the setup into a fragile experiment.