Self Hosting Vaultwarden and Setting up SSL Certificates under Tailscale in Nixos

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.