Home About Me

Locked Out of GitHub by My Own 2FA: A Recovery Story

The setup sounded sensible: GitHub was enforcing two-factor authentication, so I used a self-hosted Vaultwarden instance, an open-source Bitwarden alternative, to generate and store my TOTP codes. Everything stayed under my control. It was convenient, and it felt safer than handing another secret to a cloud service.

Then the NAS storage pool fell into read-only mode. Docker services stopped, and Vaultwarden went down with them. That was the moment the security loop closed on me. I had not backed up the TOTP seed. I had also ignored GitHub's 16-digit recovery codes. My setup depended on a single device, and GitHub's SMS recovery was not available to me. Access to my digital assets was gone.

The recovery options that were not there

<table> <thead> <tr> <th>Recovery option</th> <th>What I had</th> </tr> </thead> <tbody> <tr> <td>Recovery codes</td> <td>Not backed up</td> </tr> <tr> <td>Backup device</td> <td>None; single-device setup</td> </tr> <tr> <td>SMS verification</td> <td>Not supported</td> </tr> </tbody> </table>

I had built a system with no spare key.

An SSH key I had forgotten became the way in

I started reading about GitHub's account recovery paths. Beyond the obvious methods, there were three alternatives worth checking:

  • Device verification, which depends on a browser that still has the right cookie
  • SSH key verification
  • A personal access token (PAT)

The SSH key I had configured when deploying my blog turned out to be the opening. The recovery flow was:

  1. Open GitHub's recovery page and choose Try 2FA account recovery.
  2. Receive a one-time password at the registered email address.
  3. Choose SSH Key as the verification method.
  4. Run the command below to get the verification code:
# Key step: SSH key verification
ssh -T [email protected] verify
  1. Submit the request for review. In my case, the actual processing time was 36 hours.

Rebuilding the setup with real redundancy

As soon as I was back in, I regenerated the TOTP seed and made multiple backups of the recovery codes: electronic, paper, and an encrypted USB drive. I also redrew the backup architecture: backup architecture

The principles I now follow:

  • 3-2-1 backup rule: three copies, two types of media, one offline.
  • Isolate critical credentials: do not store TOTP seeds in the same place as the password manager.
  • Test recovery regularly: run a restore drill every quarter to check that backups actually work.

Self-hosting is powerful, but it moves the failure point

This experience made the trade-offs concrete. A self-hosted setup has clear strengths and clear weaknesses.

<table> <thead> <tr> <th>Risk</th> <th>Self-hosted</th> <th>Cloud service</th> </tr> </thead> <tbody> <tr> <td>Availability</td> <td>Single point of failure</td> <td>High availability</td> </tr> <tr> <td>Control</td> <td>Full autonomy</td> <td>Vendor dependence</td> </tr> <tr> <td>Maintenance</td> <td>Requires expertise</td> <td>Maintenance-free</td> </tr> </tbody> </table>

I still value self-hosting, but I no longer treat it as automatically safer. A service can be private and still be fragile. The same goes for authentication in general. The direction of travel includes hardware security keys using FIDO2/WebAuthn, biometric integrated authentication, and decentralized identity systems. Those tools may reduce some risks, but they do not remove the need for fallback paths.

Five things every developer should do now

  1. Print recovery codes and put them in a safe or with a trusted person.
  2. Set up redundant factors, such as Authy plus a hardware key.
  3. Build an escape route: at least two recovery methods should exist.
  4. Use tiered protection: critical accounts deserve a separate security setup.
  5. Audit the authentication system every six months.

The most dangerous security illusion is the belief that this will not happen to you. I believed my setup was complete until it locked me out. The fix is not more confidence in a single tool; it is planning for the day that tool fails.