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:
- Open GitHub's recovery page and choose
Try 2FA account recovery. - Receive a one-time password at the registered email address.
- Choose
SSH Keyas the verification method. - Run the command below to get the verification code:
# Key step: SSH key verification
ssh -T [email protected] verify
- 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:

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
- Print recovery codes and put them in a safe or with a trusted person.
- Set up redundant factors, such as Authy plus a hardware key.
- Build an escape route: at least two recovery methods should exist.
- Use tiered protection: critical accounts deserve a separate security setup.
- 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.