I have been using pass but since i am experimenting with selfhosting and wanted to store passkeys inside my password manager i wanted to selfhost Vaultwarden (a Bitwarden server implementation) server but i didn’t wanted to expose it to internet so i wanted to use my Tailscale network.
so i added required config to my Raspberry 5’s Nixos config
services.vaultwarden = {
enable = true;
};
and now i needed an easier way to access it then with ip and port so i needed to setup a reverse proxy like nginx, since i am using Tailscale i wanted to use MagicDNS feature of it but after looking at it i noticed i can’t use subdomains in it to host multiple services insede one machine since i want to try to selfhost things like nextcloud too on that machine i decided to not use MagicDNS to access my services.
Since i have a domain i decided to use that and point to static Tailscale ip of Vaultwarden server but my domain uses HSTS and HTTPS needded for some feutures of Bitwarden so i needed SSL Certificates for my Vaultwarden instance.
It would have been easy if it were a service exposed to the internet since Nixos has support for automatically getting certificate from Let’s Encrypt and automatically renewing them but it uses HTTP-01 challenge by default to prove ownership of the domain so i needed a better solution since the IP of the domain wasn’t exposed to the internet. Solution was using DNS-01 challenge since it uses dns records to prove ownership it doesn’t need the domain to be accessible from the internet and the best part is Nixos supported using DNS-01 challenge when configured with dns api key etc.
I added my cloudflare api key to my sops-nix repo and added them to my rpi5 secrets definitions:
sops = {
secrets = {
CLOUDFLARE_API_KEY = { };
CLOUDFLARE_EMAIL = { };
};
};
and then configured acme settings to use DNS-01 challenge:
security.acme = {
acceptTerms = true;
defaults = {
email = "alper@alper-celik.dev";
dnsProvider = "cloudflare";
credentialFiles =
let
s = config.sops.secrets;
in
{
CLOUDFLARE_API_KEY_FILE = s.CLOUDFLARE_API_KEY.path;
CLOUDFLARE_EMAIL_FILE = s.CLOUDFLARE_EMAIL.path;
};
dnsResolver = "1.1.1.1:53";
};
};
and lastly i added Nginx reverse proxy config:
services.nginx.enable = true;
services.nginx.virtualHosts."bitwarden.lab.alper-celik.dev" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.vaultwarden.config.ROCKET_PORT}";
};
};
Then i deployed my changes and it didn’t used DNS-01 challenge and failed,
after a bit of digging i found out i needed to set acmeRoot
to null
so i added that:
services.nginx.virtualHosts."bitwarden.lab.alper-celik.dev".acmeRoot = null;
After deploying again i had access to it inside my Tailscale network.
But i wasn’t over yet, i still needed to migrate 30 or so passwords from pass, after searching a bit i came across this python script: pass2bitwarden after reviewing its code i converted my password store to bitwarden format and it migrated almost everything including 2fa codes but it didn’t migrated custom fields so i manually migrated them.
and that was it. I now had a bit more polished password manager that i hosted. It was fun to setup.