smol projects

local certs with reverse proxy (mkcert + nginx)

12.08.2023 4 min read

Creating local certs with mkcert

Why do this?


  • Add a layer of security
  • Make browsers happy
    • Get rid of warnings once and for all (passwords, etc. Idk)
    • That unlock icon in the address bar becomes a nice locked icon
  • For learning purposes

Installing mkcert for Linux

sudo apt install libnss3-tools

Install homebrew if it isn’t installed already:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Add to PATH:

(echo; echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"') >> /home/<YOUR-USERNAME>/.zprofile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

Install dependency stuff as root: sudo apt-get install build-essential

Finally, brew install mkcert.

Creating the cert

Straight from the docs:

$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊

Create the cert:

mkcert home.arpa "*.home.arpa"

Created a new certificate valid for the following names 📜
 - "home.arpa"
 - "*.home.arpa"

Reminder: X.509 wildcards only go one level deep, so this won't match a.b.home.arpa ℹ️

The certificate is at "./home.arpa+1.pem" and the key at "./home.arpa+1-key.pem" ✅

It will expire on 12 November 2025

Nice. Next we need to create the nginx entries. I knew I was going to have to do this multiple times, so…

Convenience script to generate reverse proxy


while getopts "d:i:p:c:k:" opt; do
    case $opt in
    d) domain="$OPTARG" ;;
    i) ip="$OPTARG" ;;
    p) port="$OPTARG" ;;
    c) cert_path="$OPTARG" ;;
    k) key_path="$OPTARG" ;;

if [ -z "$domain" ] || [ -z "$ip" ] || [ -z "$port" ] || [ -z "$cert_path" ] || [ -z "$key_path" ]; then
    echo "Usage: $0 -d <domain> -i <ip> -p <port> -c <certificate_path> -k <key_path>"
    exit 1


# Define color codes

# Text
UNDERLINE=$(tput smul)

# Pretty print stuff
printf "${YELLOW}${UNDERLINE}Configuration Summary${RESET}\n"
printf "${YELLOW}Domain:${RESET} $domain\n"
printf "${YELLOW}IP:${RESET} $ip\n"
printf "${YELLOW}Port:${RESET} $port\n"
printf "${YELLOW}Certificate Path:${RESET} $cert_path\n"
printf "${YELLOW}Key Path:${RESET} $key_path\n"

read -p "Continue (y/n)? " CONT
CONT=${CONT,,} # Convert to lowercase

if [ "$CONT" = "y" ]; then
    echo "server {
    listen 80; # listen on IPv4
    listen [::]:80; # listen on IPv6

    server_name $domain;

    location / {
        proxy_pass http://$ip:$port;
        include proxy_params;

    listen 443;
        listen [::]:443;

    ssl_certificate $cert_path;
    ssl_certificate_key $key_path;
}" >"$nginx_config"
    ln -s "$nginx_config" "/etc/nginx/sites-enabled"
    systemctl reload nginx
    echo "nginx configuration created for $domain at $nginx_config"
    exit 0

Example usage:

sudo ./genproxy.sh -d site.home.arpa -i 192.xxx.x.xxx -p 3000 -c /path/to/home.arpa+1.pem -k /path/to/home.arpa+1-key.pem

Local DNS entry in /etc/hosts

# .arpa
192.xxx.x.xxx home.arpa
# End of section

I still don’t have local DNS set-up, and I might never get around to it.

If you’re accessing your sites / services from the same machine, that should be all there is to it. However, in my case, I’m using Linux as a server and want to access my services from my Windows machine. So… extra steps.

Installing CA on another machine

Move the .pem file from host to target:

rsync -avzh /home/<YOUR-USERNAME>/.local/share/mkcert/rootCA.pem wsl:/home/<YOUR-USERNAME>/.local/share/mkcert

Now it exists on the target machine. mkcert -install and curl shows that it works successfully, but in order to have browsers recognize the cert - we need to install it for Windows (because the previous step was for WSL).

Installing on Windows

First, choco install mkcert

Next, run mkcert -CAROOT to determine where the CA files should be stored. It will be something like C:\Users\<YOUR-USERNAME>\AppData\Local\mkcert.

Move the .pem file from WSL to Windows filesystem:

cp /home/<YOUR-USERNAME>/.local/share/mkcert/rootCA.pem /mnt/c/Users/<YOUR-USERNAME>/AppData/Local/mkcert/

On Powershell (or whatever equivalent):

  • mkcert install
  • Click on “Yes”

Based on my testing, this should be enough for Brave and Chrome. However, it seems we need to manually install the cert for Firefox.

Manual import of .pem in Firefox

  • On the top right, click on the hamburger icon > Settings
  • Privacy & Security > Certificates > View Certificates
  • Click on “Import” and add the .pem manually


Built with Astro and Tailwind 🚀