Exposing the Instance Securely
Make `spl serve` reachable from other machines without putting an unauthenticated SQLite-backed HTTP server on the public internet. Tailscale, reverse proxy with TLS, CORS, and what never to do.
This page is for people running their own workspace or using the API. If you use a workspace created at syncropel.com, start with Your hosted workspace.
By default spl serve binds 127.0.0.1 and is unreachable from anywhere else on your network. You'll want to change that when:
- You're pairing a phone that lives on the same home network.
- You're running a peer workspace that needs to sync with another host.
- You want to use the CLI from a second machine.
- You're hosting a shared team instance.
Three safe paths, and one you should never take.
Preferred: Tailscale (or any WireGuard-based private network)
Tailscale gives each machine an address in the 100.x.y.z space. The addresses are only routable between machines you've explicitly joined to your tailnet. No public IP is exposed. Access control is built in.
Install Tailscale on the instance host and any device that needs to reach it. Then:
# Find the host's tailnet address
tailscale ip -4
# Bind the instance to that address
spl stop
spl serve --host 100.x.y.zDevices on the same tailnet reach the instance at http://100.x.y.z:9100. That becomes the --url you pass to spl pair.
Tailscale's ACLs further narrow which devices can reach the instance. Default-allow across your own tailnet is fine for personal use; tighten for shared machines.
Alternative: reverse proxy with TLS
A reverse proxy (Caddy, nginx, or Traefik) terminates TLS on a public hostname and forwards to the instance on loopback. This is the right answer when you need a public URL (say, https://syncropel.your-domain.com) for a peer workspace or a web dashboard hosted elsewhere.
Keep the instance bound to loopback:
spl serve # stays on 127.0.0.1:9100Caddy example
syncropel.your-domain.com {
reverse_proxy 127.0.0.1:9100
}Caddy handles certificate issuance via Let's Encrypt automatically.
nginx example
server {
listen 443 ssl;
server_name syncropel.your-domain.com;
ssl_certificate /etc/letsencrypt/live/syncropel.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/syncropel.your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9100;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Server-Sent Events for /v1/sync/subscribe
proxy_buffering off;
proxy_read_timeout 3600s;
}
}Instance-side hardening when behind a proxy
When a proxy terminates connections, the instance sees 127.0.0.1 as the client IP for everything. Two config flags harden against header spoofing in this mode:
# strict impersonation enforcement has a dedicated verb:
spl config impersonation-strict-enablestrict_actor_header has no dedicated verb yet; enable it by writing a
strict_actor_header config record (a LEARN on th_engine_config with
enabled: true, the standard config-record convention). Both take effect on the
next request: no restart needed.
strict_actor_headerrejects requests that claim a DID via header without a matching token.strict_impersonation_enforcementblocks admin tokens from acting as arbitrary actors unless an explicit allowlist is set.
Alternative: an outbound tunnel
A variant of the reverse-proxy pattern that doesn't require opening a port on your firewall. The instance stays on loopback; a tunnel agent establishes an outbound connection to a tunnel service, which terminates TLS on a public hostname and forwards traffic back down the tunnel to http://127.0.0.1:9100.
Several tunnel services offer this. Follow your provider's setup, point the tunnel's ingress at http://127.0.0.1:9100, and the same hardening flags apply (strict_actor_header, strict_impersonation_enforcement).
Do not: public internet without TLS
# NEVER
spl serve --host 0.0.0.0On a cloud VM, --host 0.0.0.0 exposes the instance on port 9100 to the entire internet. Even with auth on, you are one config mistake away from a token leak that compromises the whole record store. Even with perfect auth hygiene, you're paying every bot on the internet to probe your instance's surface.
If you find yourself wanting --host 0.0.0.0, pick one of the three safer alternatives above. The only legitimate use of 0.0.0.0 is on a host that is itself behind a firewall, reverse proxy, or tunnel, and in that case, --host 127.0.0.1 would also work and is simpler.
CORS: when a browser calls your instance
The instance's HTTP API rejects cross-origin requests by default. To let a browser at https://syncropel.com (or any other origin) call your locally-running instance, add the origin to the allowlist:
spl config auth-set-cors-origins https://syncropel.comMultiple origins:
spl config auth-set-cors-origins https://syncropel.com,https://app.example.comThe setting is a runtime record: it takes effect immediately and survives instance restarts.
Typical setups:
syncropel.comcalling your instance. Addhttps://syncropel.com.- A private web app calling a shared team instance. Add the app's origin.
- Developer localhost (rare). Add
http://localhost:<port>.
Verifying CORS
From the browser's devtools network tab, the instance's responses should include Access-Control-Allow-Origin matching the request's Origin. If they don't, the request is either coming from an origin not on the allowlist, or you haven't set the allowlist at all. spl config show lists current settings.
Pairing with another workspace
Once the instance is reachable on a stable URL, another workspace can pair with it. The pairing flow creates reciprocal federation:manage-scoped service accounts, exchanges public keys, and persists a pair record on both sides. See the workspace pairing guide for the canonical spl federation pair <peer-url> walkthrough and the Discovery guide for auto-discovery via DNS or mDNS.
Decision matrix
| Use case | Recommended path |
|---|---|
| Phone on same home Wi-Fi | Bind to LAN IP, or use Tailscale |
| CLI from a second personal machine | Tailscale |
Browser on syncropel.com | Loopback instance + CORS allowlist |
| Peer workspace on a cloud host | Reverse proxy with TLS or an outbound tunnel |
| Team-shared instance | Reverse proxy with TLS on a private network |
| Single developer laptop | Loopback only: don't expose at all |
See also
- Authentication and service accounts: scopes, token lifecycle, emergency recovery
- Workspace pairing guide: pair two instances for bi-directional sync via
spl federation pair - Operator runbook: backup discipline, recovery, upgrades
- Troubleshooting: when an exposed instance isn't reachable or CORS is blocking
Starting Your Instance
The three modes `spl serve` runs in (dev, secure local, and exposed) and how to manage your instance's lifecycle. Start, stop, restart, inspect, tail logs.
Service Accounts and Tokens
Delegate scoped capability to browsers, paired phones, MCP agents, remote CLIs, and peer workspaces. Service accounts are the delegation primitive; for first-run on Linux/macOS you don't need one.