--- title: "Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI" domain: troubleshooting category: general tags: [forgejo, gitea, smtp, docker, account-recovery, self-hosting] status: published created: 2026-06-12 updated: 2026-06-12 --- # Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI Two related problems on a single-admin self-hosted **Forgejo** (or Gitea): the GUI *"Forgot password"* is disabled, and you can't log in to fix it. Here's how to (1) enable account recovery properly, and (2) recover from the command line when you're already locked out. ## Symptoms - The *Forgot password* page shows: **"Account recovery is only available when email is set up. Please set up email to enable account recovery."** - You can't log in (wrong/forgotten password), so you can't add an SSH key or change settings in the GUI either. ## Part 1 — Enable account recovery (configure the mailer) Account recovery needs SMTP. If you already run a mail server on your tailnet, relay through it — **no app password needed** when the Forgejo host is `mynetworks`-trusted by that mail server. Edit `app.ini` (in the data volume, e.g. `/data/gitea/conf/app.ini`): ```ini [mailer] ENABLED = true PROTOCOL = smtp+starttls SMTP_ADDR = 100.x.y.z ; mail server's tailnet IP SMTP_PORT = 587 FROM = forgejo@example.com FORCE_TRUST_SERVER_CERT = true ; required when connecting by IP (cert CN won't match) ``` Notes: - `FORCE_TRUST_SERVER_CERT = true` is needed when you target the relay by **IP** — the TLS cert is issued for a hostname, not the IP, so verification would otherwise fail. Acceptable on a trusted internal hop. - Omit `USER`/`PASSWD` if the relay accepts your host via `mynetworks` (no SASL). Otherwise add SMTP auth. - `app.ini` lives in the persistent volume, so the change **survives container re-creation** (e.g. Watchtower's nightly pull). Apply and verify: ```bash docker restart forgejo docker logs forgejo 2>&1 | grep -i "Mail Service Enabled" # confirms the mailer loaded ``` Test the SMTP path **before** trusting it (run from the host, mimicking Forgejo's connection): ```bash python3 - <<'EOF' import smtplib, ssl ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE s = smtplib.SMTP("100.x.y.z", 587, timeout=15) s.ehlo(); s.starttls(context=ctx); s.ehlo() s.sendmail("forgejo@example.com", ["you@example.com"], "Subject: test\r\n\r\nForgejo relay path test") s.quit(); print("SENT_OK") EOF ``` `SENT_OK` means the relay accepted the message. `/user/forgot_password` should now show the reset form instead of the email error. > **Container can't reach the tailnet IP?** Docker bridge networks usually route to Tailscale via the host (SNAT to the host's tailnet IP). Confirm with: > `docker exec forgejo nc -w5 100.x.y.z 587 -p '' ``` > ⚠️ **Gotcha:** `change-password` sets `must_change_password=true` by default. That **forces a change on next GUI login _and_ returns HTTP 403 on the API** (`"You must change your password"`). Clear it: > ```bash > docker exec -u 1000 forgejo forgejo admin user must-change-password --unset > ``` **Add an SSH key without the GUI** (basic-auth API — works only if 2FA is off): ```bash curl -u :'' -X POST -H 'Content-Type: application/json' \ -d '{"title":"laptop","key":"ssh-ed25519 AAAA... you@host"}' \ http://localhost:3004/api/v1/user/keys # HTTP 201 = created ``` Forgejo regenerates the git user's `authorized_keys` from the database, so `ssh -p git@host` authenticates immediately afterward — no restart needed. ## "The password keeps changing" — it (probably) isn't If a self-hosted Forgejo admin password *seems* to reset itself, a stock Forgejo container does **not** reset admin passwords. Rule out the server first: - the compose has **no** admin/password env and no custom entrypoint; - **no** cron, systemd timer, or script runs `forgejo admin user change-password`; - the data volume is persistent (re-creation keeps the DB, password included). If all three hold, nothing server-side is changing it — the "changing" password is a **client-side** artifact: a duplicate or stale entry in your password manager autofilling different values. Delete the duplicates and keep one. ## See also - Forgejo — [Config Cheat Sheet → mailer](https://forgejo.org/docs/latest/admin/config-cheat-sheet/)