The GitHub App is optional on a self-hosted instance. Vault users can sign in with email without it. Create one GitHub App for the instance only if you want GitHub sign-in or per-user vault backup. Those users then install that app on one repository. Syncidian always uses the main branch. Personal access tokens and deploy keys are not used.
Do this after the server is reachable at a URL you will keep (for example https://syncidian.example.com or http://localhost:8080 for a private laptop).
The operator UI is SYNCIDIAN_ADMIN_PATH (default /admin) on the same URL as the public site. Self-hosters can skip Tailscale: leave SYNCIDIAN_ADMIN_HOST unset, or set SYNCIDIAN_ADMIN_PRIVATE=0. A private hostname (SYNCIDIAN_ADMIN_HOST, for example https://admin.syncidian.com) is optional. It is not linked from /. Enable Stats for Nerds on that page to see GitHub App URLs and credential paste. Mesh lock-down: Hide admin.syncidian.com on Tailscale.
GitHub asks for three URLs. Replace {base} with your public origin, with no trailing slash:
| GitHub field | URL | Why GitHub asks for it |
|---|---|---|
| Callback URL (User authorization callback URL / redirect URI) | {base}/api/v1/auth/github/callback |
After Sign in with GitHub, GitHub sends the browser here. |
| Setup URL | {base}/api/v1/github/app/setup |
After someone installs the app, GitHub sends them here so Syncidian can bind that installation to their account. |
| Webhook URL | {base}/api/v1/github/app/webhook |
GitHub requires a webhook URL so it can ping the app when you create or update it. Syncidian answers that ping with HTTP 200. You do not need extra webhook events for backup to work. |
Copy the filled-in values from /admin after you create the first admin, or from:
GET {base}/api/v1/github/app/urls
Set SYNCIDIAN_PUBLIC_URL to {base} if the server sits behind a reverse proxy, so the GitHub App URLs (callback, setup, webhook) match the hostname GitHub can reach. A Tailscale-only operator hostname is not that origin.
If the public hostname changes (for example Railway *.up.railway.app → https://syncidian.com), keep the same GitHub App. App ID, Client ID, client secret, and private key do not change. Open the app’s settings and set Homepage, Callback, Setup, and Webhook to the new {base} URLs above. Creating a new app would orphan existing installs.
This posts a GitHub App manifest, so permissions and URLs are filled in for you.
- Start Syncidian (Docker Compose,
go run ./cmd/syncidian serve, or your host). - Open
{base}/admin. - Create the first admin (username + password of at least 8 characters).
- On the admin overview, click Create GitHub App.
- GitHub opens Create a new GitHub App. Review the name if you want, then click Create GitHub App.
- GitHub redirects back to Syncidian.
/adminshould show the app as Registered.
Those credentials are stored encrypted in SQLite. Attach a volume at /data (or copy them into SYNCIDIAN_GITHUB_APP_* env vars — Option B) so the next deploy does not wipe the GitHub App. Set SYNCIDIAN_DATA_KEY if you want the encryption key in environment variables instead of data/secret.key.
People can now use Continue with GitHub / Log in / Connect to your GitHub repository on {base}/. Self-hosted email login already works without this app. On hosted Syncidian.com those GitHub buttons stay hidden until you tap the app name six times, and SYNCIDIAN_GITHUB_ALLOWED_EMAILS must include the GitHub account.
The manifest requests:
- Repository permissions: Contents read and write, Metadata read
- Account permissions: Email addresses read (so sign-in can store an email)
- Request user authorization (OAuth) during installation: on
- Webhook: active, URL as above, event
push(unused for the ping; backup uses installation tokens, not webhook payloads)
Use this when GitHub’s manifest flow is blocked, or you want the credentials in environment variables instead of the server database.
- Sign in to GitHub as the account or organization that should own the app.
- Open GitHub Apps → New GitHub App (organization: Settings → Developer settings → GitHub Apps).
- GitHub App name: something like
Syncidian(must be unique on GitHub). - Homepage URL:
{base} - Callback URL:
{base}/api/v1/auth/github/callback
Expire user authorization tokens: optional. - Check Request user authorization (OAuth) during installation.
- Setup URL:
{base}/api/v1/github/app/setup
Check Redirect on update if GitHub shows it, so changing an install also returns to Syncidian. - Webhook: active. Webhook URL:
{base}/api/v1/github/app/webhook. Secret: optional (Syncidian does not verify a secret today). - Repository permissions:
- Contents: Read and write
- Metadata: Read-only (required)
- Account permissions:
- Email addresses: Read-only
- Where can this GitHub App be installed?
- Only on this account — fine if you are the only GitHub user who will install it.
- Any account — required if other people on this Syncidian instance must install it on their GitHub users or orgs.
- Click Create GitHub App.
On the app’s settings page:
- Note App ID (number) and the slug only (
<slug>fromhttps://github.com/apps/<slug>— do not paste the full URL). - Client ID is shown on the page. Generate a new client secret and copy it once.
- Private keys → Generate a private key. Download the
.pemfile.
Environment variables (restart the process after setting them):
export SYNCIDIAN_PUBLIC_URL="https://syncidian.example.com"
export SYNCIDIAN_GITHUB_APP_ID="123456"
export SYNCIDIAN_GITHUB_APP_SLUG="syncidian"
export SYNCIDIAN_GITHUB_CLIENT_ID="Iv1.xxxxxxxx"
export SYNCIDIAN_GITHUB_CLIENT_SECRET="xxxxxxxx"
# Literal \n sequences are turned into real newlines:
export SYNCIDIAN_GITHUB_APP_PRIVATE_KEY="$(sed ':a;N;$!ba;s/\n/\\n/g' /path/to/syncidian.private-key.pem)"Docker Compose can pass the same names through. A one-line PEM:
environment:
SYNCIDIAN_GITHUB_APP_ID: "123456"
SYNCIDIAN_GITHUB_APP_SLUG: "syncidian"
SYNCIDIAN_GITHUB_CLIENT_ID: "Iv1.xxxxxxxx"
SYNCIDIAN_GITHUB_CLIENT_SECRET: "xxxxxxxx"
SYNCIDIAN_GITHUB_APP_PRIVATE_KEY: "-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----"Env vars override a GitHub App stored in the database.
Keep the .pem and client secret off the public site. /admin never shows another user’s vault; it also should not print these secrets.
Device sync works with no GitHub at all. GitHub is optional backup, one repo per user.
- Open
{base}/(not/admin). - Sign in with email (self-host) or Continue with GitHub (on the hosted site, tap the Syncidian name six times first), then Connect to your GitHub repository.
- Authorize the app, then Install it on one repository (or several, then pick one in the dashboard).
- Syncidian always commits to
main. Other branches are not used. - Create a
sk_sync_…token on that user’s Tokens page and paste it into the Obsidian plugin. The plugin never sees GitHub credentials.
If GitHub sent them back after Install & Authorize, Syncidian records the installation_id on the OAuth callback (/api/v1/auth/github/callback). That happens when the app has Request user authorization (OAuth) during installation enabled — GitHub then uses the callback URL instead of the Setup URL. The Setup URL still works for installs without that option.
- Callback and setup only need to work in the browser that signs in.
http://localhost:8080/...is valid for a single-machine install. - Webhook pings come from GitHub’s servers. They cannot reach
localhost. You can still create the app; GitHub may show failed deliveries. Sign-in andgitbackup do not wait on those pings. - For a phone or another PC, use a real HTTPS hostname (or a tunnel) as
{base}and put that same origin in the three URLs. Then click Create GitHub App again, or edit the existing app’s URLs on GitHub to match.
| Symptom | Likely cause |
|---|---|
| “This instance has no GitHub App yet” | Finish Option A or B. Confirm /admin shows Registered, or the SYNCIDIAN_GITHUB_APP_* variables are set. |
| OAuth error / redirect mismatch | Callback URL on the GitHub App must be exactly {base}/api/v1/auth/github/callback. {base} must be the origin in the address bar (scheme + host + port). |
| Redirect URI error after changing the public domain | App ID, Client ID, client secret, and private key stay the same. Edit the existing GitHub App: Homepage, Callback, Setup, and Webhook must all use the new {base} (for hosted: https://syncidian.com). Do not create a new app. |
| Install succeeds but Syncidian says credentials are missing | Setup URL must be {base}/api/v1/github/app/setup. Sign in as a non-admin vault user; admins do not connect a repo. |
| Install & Authorize succeeds but dashboard still says Not configured | With OAuth during installation, GitHub redirects to the Callback URL with installation_id. Syncidian must receive that on /api/v1/auth/github/callback while you are signed in. Click Connect with GitHub again from the dashboard (do not open the install URL in a private window). Confirm Callback URL is exactly {base}/api/v1/auth/github/callback. |
| Cannot install on someone else’s GitHub account | Make the app installable on Any account (public to GitHub users, not the Marketplace). |
| Push/pull fails | Contents must be read and write. The install must include the chosen repo. Branch must be main. |
Connect with GitHub opens a GitHub 404 (github.com/apps/https://…) |
SYNCIDIAN_GITHUB_APP_SLUG (or the admin slug field) was set to a full URL. Use only the slug (syncidian), not https://github.com/apps/syncidian. Syncidian now normalizes pasted URLs, but re-save credentials or fix the env var and restart. |
Admins never connect a vault repository and cannot see another user’s GitHub credentials.