-
Notifications
You must be signed in to change notification settings - Fork 66
en Security
Overview · Features · Tech Stack · Quick Start · Deployment · Project Structure · Architecture · Widgets · API · Database · Settings · Security · Configuration · Roadmap · License
- Without Cloudflare Access, instances must complete initial onboarding to set login credentials—there is no unauthenticated “open mode”
- Access mode is a login gate only; all verified requests use the built-in
defaultuser data - HTTP Basic Auth credentials live in D1
basic_auth_credentials(password stored as a PBKDF2 hash, not plain text) - SSH passwords/keys are stored in D1
credentials(per server); vault entries insaved_passwords/saved_private_keys - Once Basic Auth is enabled, every path and asset requires authentication first; responses include
X-Robots-Tag: noindexto reduce search-engine indexing - Full-site HTTPS / WSS; DO instances isolated per session
ternssh picks one of three modes based on whether Cloudflare Access is configured and whether Basic Auth credentials exist in D1:
| Mode | Condition | Description |
|---|---|---|
| onboarding | Access not configured, no Basic Auth yet | First visit shows setup page to create username and password |
| basic | Access not configured, Basic Auth set up | Browser HTTP Basic Auth; credentials from the database |
| access |
ACCESS_TEAM_DOMAIN + ACCESS_AUD configured |
Cloudflare Zero Trust JWT verification |
When Access is configured, database Basic Auth credentials are not used. Set Access variables in the Workers Dashboard or Docker
.dev.vars—not inwrangler.production.jsonc.
When Cloudflare Access is not configured and no login credentials exist in the database, the instance enters onboarding:
- Choose a username
- Set a password and confirm it
- Credentials are written to D1
basic_auth_credentials - The page reloads and the browser shows the Basic Auth login prompt
Local dev (npm run dev:server), Docker, and self-hosted Workers all follow the same flow.
For instances deployed to Cloudflare Workers. Access intercepts unauthenticated requests at the edge; after login Cloudflare injects a Cf-Access-Jwt-Assertion header that ternssh validates.
- Open Cloudflare Zero Trust → Access → Applications
- Click Add an application → choose Self-hosted
- Set an application name (e.g.
ternssh) - Set Session Duration as needed
- Under Application domain, enter the exact URL users visit, for example:
-
ternssh.your-subdomain.workers.dev(workers.devsubdomain) -
ssh.example.com(custom domain)
-
- Add a Policy (e.g. Allow → Emails ending in
@yourcompany.com, or One-time PIN) - Save the application
workers.devand custom domains are separate Application domains—create one Access app per URL and use matching AUD tags.
| Item | Where to find it |
|---|---|
| AUD | Application details → Application Audience (AUD) Tag (64-char hex) |
| Team Domain | Zero Trust → Settings → Custom pages → Team domain, e.g. your-team.cloudflareaccess.com (no https://) |
Cloudflare Dashboard → Workers & Pages → your ternssh Worker → Settings → Variables and Secrets:
| Name | Type | Value |
|---|---|---|
ACCESS_TEAM_DOMAIN |
Variable (plain text) | your-team.cloudflareaccess.com |
ACCESS_AUD |
Secret (recommended) or Variable | AUD Tag from step 2 |
Changes take effect immediately—no redeploy required.
- Visit your Worker URL in a browser
- You should be redirected to the Cloudflare Access login page first
- After passing the policy, the ternssh dashboard loads
A 401 with Missing Cf-Access-Jwt-Assertion usually means the Application domain does not match the URL you visit, or variables are misconfigured.
Copy .dev.vars.example to .dev.vars:
ACCESS_TEAM_DOMAIN=your-team.cloudflareaccess.com
ACCESS_AUD=your-64-char-aud-tagLocal wrangler dev does not go through the Access login page; you need a valid JWT to test Access mode locally—mainly useful for checking variable format.
Basic Auth is not configured via environment variables. Credentials are created during onboarding and stored in D1; the server validates them on each request.
- Docker / self-hosted
- Workers instances without Cloudflare Access
After first start, visit the instance URL and complete onboarding. Credentials persist in the local D1 database inside the /app/.wrangler volume.
docker compose -f docker-compose.ghcr.yml up -d
# or from source: docker compose up -d --builddocker run -d \
--name ternssh \
-p 8787:8787 \
-v ternssh-data:/app/.wrangler \
ghcr.io/haradakashiwa/ternssh:latestIn Basic Auth mode, open Settings → Security to:
- Change username (requires current password)
- Change password (requires confirmation)
- Sign out (clears the browser’s cached Basic Auth credentials)
After saving credential changes, you are signed out automatically and must log in again with the new credentials.
3 failed password attempts from the same IP lock access for 1 hour (via CF-Connecting-IP; cleared on successful login).
| Endpoint | Description |
|---|---|
GET /api/v1/onboarding/status |
Current auth mode (anonymous during onboarding) |
POST /api/v1/onboarding/setup |
Initial credential setup |
GET /api/v1/auth/credentials |
Current username |
PUT /api/v1/auth/credentials |
Update username/password |
POST /api/v1/auth/logout |
Sign out |