Covers enabling the [mailer] for password recovery (relay via a tailnet mail server, no-auth/mynetworks, FORCE_TRUST_SERVER_CERT for IP targets), CLI password reset + the must-change-password=true gotcha, adding an SSH key via the basic-auth API when locked out, and ruling out a server-side cause for a 'changing' password.
4.6 KiB
| title | domain | category | tags | status | created | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI | troubleshooting | general |
|
published | 2026-06-12 | 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):
[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 = trueis 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/PASSWDif the relay accepts your host viamynetworks(no SASL). Otherwise add SMTP auth. app.inilives in the persistent volume, so the change survives container re-creation (e.g. Watchtower's nightly pull).
Apply and verify:
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):
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 </dev/null && echo REACHABLE
Part 2 — Recover from the CLI (already locked out)
Forgejo's admin CLI runs inside the container as the git user (UID 1000) and needs no login.
Reset a password:
docker exec -u 1000 forgejo forgejo admin user change-password -u <user> -p '<newpass>'
⚠️ Gotcha:
change-passwordsetsmust_change_password=trueby default. That forces a change on next GUI login and returns HTTP 403 on the API ("You must change your password"). Clear it:docker exec -u 1000 forgejo forgejo admin user must-change-password --unset <user>
Add an SSH key without the GUI (basic-auth API — works only if 2FA is off):
curl -u <user>:'<pass>' -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 <port> 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