Create the GitHub App
This guide shows you how to give your server its own GitHub identity. It takes about two minutes, only the server owner does it, and it only has to happen once per server.
For connecting your own account afterwards, see Set up GitHub integration.
Why your server needs its own app
Section titled “Why your server needs its own app”Two different jobs come out of one registration, and the app is what makes both honest:
| Job | What answers it | Why it matters |
|---|---|---|
| A person signs in | The app’s client id, over the device flow | You get a credential that is yours, on the server, on every device — with no token to paste or rotate |
| The server acts | The app’s id + private key → installation token | Webhooks, PR polling and ticket sync stop depending on one human’s token still being valid |
Note what is not in that table: a client secret. GitHub’s device flow authenticates with the client id alone — both the device-code request and the token poll — so there is no secret to keep for signing in.
Without an app of your own, the first still works through the client id the build ships; the second falls back to the server owner’s credential, so background work rides on whoever onboarded first.
Prerequisites
Section titled “Prerequisites”- You are the server owner — the
providerApps.*operations are gated to it, and the card is invisible to everyone else - A GitHub account, or an organization you can create apps in
- Nothing publicly reachable: the sign-in is a device flow, so the server needs no inbound port, tunnel, callback URL or domain
Register the app
Section titled “Register the app”- Go to github.com/settings/apps/new (for an org: Settings → Developer settings → GitHub Apps → New GitHub App)
- GitHub App name — anything;
Control Center (yourname)is fine. It shows up on commits and comments the server makes - Homepage URL — anything, e.g.
https://github.com/your-name - Callback URL — leave it empty. The device flow has none
- Tick Enable Device Flow. This is the one setting the sign-in cannot work without
- Untick Webhook → Active unless you are wiring webhooks separately
- Under Repository permissions, grant at least:
- Contents: Read and write
- Metadata: Read-only (mandatory)
- Pull requests: Read and write
- Issues: Read and write, if you use GitHub Issues for tickets
- Checks and Commit statuses: Read-only, for CI state on the PR page
- Under Where can this GitHub App be installed?, choose whichever fits — “Only on this account” is right for a personal server
- Click Create GitHub App
Collect the three values
Section titled “Collect the three values”On the app’s page, immediately after creation:
| Value | Where |
|---|---|
| App ID | The General tab, near the top |
| Client ID | The General tab, under “Client secrets” |
| Private key | Generate a private key — a .pem downloads |
The private key file downloads once and GitHub never shows it again. Keep it until you have pasted it; you can always generate another.
A client secret is optional and buys exactly one thing: if you leave “Expire user authorization tokens” ON, it is what renews someone’s token silently instead of asking them to sign in again every eight hours. The sign-in itself never uses it.
The simpler setup is to turn that expiry OFF, and then no secret is needed anywhere. If you keep it on, generate a secret and paste it in Settings → Server → Provider apps — there is no environment variable for it, and none is baked into a build, so it stays on your server.
Install it on your account
Section titled “Install it on your account”An app that is registered but not installed can authenticate people and read nothing.
- On the app’s page, open Install App
- Install it on your account or org
- Choose All repositories, or select the ones this server should see
Paste it into Control Center
Section titled “Paste it into Control Center”- Go to Settings → Server → Single sign-on and scroll to the Provider apps card
- On the GitHub block, set each field:
- App id — the numeric App ID
- Private key — paste the whole
.pem,BEGINandENDlines included - Client id — from the same page
- Client secret — only if you generated one (for token refresh)
- Click Test
The key is parsed as it is saved, so a truncated or wrong-format paste is rejected there and then rather than failing on a background request hours later. Test goes further and asks GitHub which accounts the app is installed on — “saved” and “works” are different claims, and only the second one means anything will run.
Checkpoint: the GitHub block reads “The server can act as itself, and people can sign in”, and the GitHub row under Settings → You → Profile & identity → Code hosting now offers Sign in with GitHub.
Configure it with environment variables instead
Section titled “Configure it with environment variables instead”For a headless or scripted install, the server seeds each field from the environment the first time it has no stored value:
GITHUB_APP_ID=123456GITHUB_APP_PRIVATE_KEY="$(cat control-center.private-key.pem)"GITHUB_CLIENT_ID=Iv1.0123456789abcdefThere are no command-line flags for these — a secret on a command line is
readable by every process on the host through ps. The environment is the only
place to configure them, and it beats whatever the build ships.
.env.template is the complete list.
For local development the repo-root .env carries the same values and the
desktop passes them to the server it spawns. Put the key on one line there:
a .env value is one line, and \n inside double quotes decodes to a newline.
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEow…\n-----END RSA PRIVATE KEY-----"GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY are the same names the sandbox
token broker already reads, so a host that configured fine-grained agent tokens
gets the sign-in lane for free. Seeding happens once per field — after that
the stored value wins, so editing in Settings is not undone by the next
restart.
Do the same for Linear
Section titled “Do the same for Linear”Linear has no device flow, so its sign-in is a browser round-trip and it does need a callback URL — the Provider apps card shows you the exact one to register.
- Create an OAuth application at linear.app/settings/api/applications/new
- Set the Callback URL to the URL the card shows, verbatim
- Paste the Client ID and Client secret into the Linear block
- Optionally add a workspace API key — that is the credential the server uses to sync tickets when no human asked
Troubleshooting
Section titled “Troubleshooting”The sign-in dialog says the provider returned no device code
Section titled “The sign-in dialog says the provider returned no device code”Enable Device Flow is off on the app. Turn it on under General and retry.
Test says the credentials work but the app is not installed anywhere
Section titled “Test says the credentials work but the app is not installed anywhere”Registration and installation are separate steps. Open Install App on the app’s page and install it.
Test says the private key could not be read
Section titled “Test says the private key could not be read”Paste the whole .pem file, including the -----BEGIN and -----END lines. An
ed25519 or EC key cannot be used — GitHub Apps sign with RSA.
A member signed in but sees no repositories
Section titled “A member signed in but sees no repositories”The app is not installed on the account that owns them, or its installation selects different repositories. Their own sign-in only grants what the app is allowed to see.