Why?
The best way to secure your homelab hosted services on your basement server is to use a VPN like wireguard. But how can you access them from devices that can’t connect to your VPN network because those are machines owned by an external entity, like your employer, and you can’t install additional software on them? You could make your services public, but then you would have to keep them constantly updated with security patches, and maintain auth rate limiting and other security mechanisms against bots and hackers.
One clever solution for this problem can be mTLS. mTLS stands for Mutual TLS, in which you, as the client, also provide an SSL certificate to your server when you want to connect. This way, the HTTP server knows that it’s you and can accept the connection. An SSL/TLS implementation in Nginx is an order of magnitude more secure than, for example, a service for IP camera surveillance of your backyard.
How?
I run most of my service behind a proxy VPS server that provides me with a static external IP and some other monitoring functionality. For the web proxy, I use nginx.
Generating certificates
To enable mTLS for a nginx virtual host you have to create a CA and generate certificates for your virtual host and each machine you are willing to access the host from.
The CA and certificate generation is identical to that described in my previous post on Simple OpenSSL Certificate Authority.
But the main difference is that the algorithm used for the certificates can not be Ed25519 as it is not implemented by many browsers (2025-06-07).
For speed and improved security I suggest to use ECDSA (for example the secp384r1
curve, also known as P-384).
To generate the private key for your CA and further certificates type:
openssl ecparam -name secp384r1 -genkey -noout -out ecdsa-p384-private-key.pem
The rest of the steps remains the same as in the previous post, so: gen-key -> req -> sign.
Installing certificates
Client
For installing the client certificate in your browser you should pack the private key and signed certificate into a PKCS#12 file:
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca_cert.pem
Then, depending on your browser, you can import your client certificate.
Server
For installing the server certificate in nginx, you should copy the CA certificate, server certificate, and server private key file to your VPS. Of course you should generate the server private key and certificate signing request on your target machine, so the private key wont leave the server. But we are not securing a government site :) .
Then, in nginx, in your virtual host configuration file, you point to the certificates. Below is an example proxy configuration using the certificates:
# Server block for HTTP to HTTPS redirection
server {
listen 80;
server_name your.virtual.host.domain.com;
# Enforce HTTPS
location / {
return 301 https://$host$request_uri;
}
# Optional: Access and error logs for HTTP traffic (can be useful for debugging redirects)
access_log /var/log/nginx/your.virtual.host.domain.com.http.access.log;
error_log /var/log/nginx/your.virtual.host.domain.com.http.error.log;
}
server {
listen 443 ssl;
server_name your.virtual.host.domain.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/your.virtual.host.domain.com.crt; # Path to your server certificate
ssl_certificate_key /etc/nginx/ssl/your.virtual.host.domain.com.key; # Path to your server private key
# mTLS Configuration
ssl_client_certificate /etc/nginx/ssl/ca_cert.crt; # Path to your CA certificate
ssl_verify_client on; # Require client certificate
ssl_verify_depth 2; # Optional: Adjust as needed for your CA chain depth
location / {
# Proxy Settings
proxy_pass http://10.21.37.10; # Assuming your upstream listens on HTTP
# Standard Proxy Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Optional: If your upstream application needs to know if mTLS was successful
# proxy_set_header SSL_CLIENT_VERIFY $ssl_client_verify;
# proxy_set_header SSL_CLIENT_S_DN $ssl_client_s_dn;
# Optional: Adjust proxy timeouts if needed
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout 60s;
}
# Optional: Access and error logs
access_log /var/log/nginx/your.virtual.host.domain.com.access.log;
error_log /var/log/nginx/your.virtual.host.domain.com.error.log;
}
Restart you nginx service and try to connect to your website. You should be prompted to use your client certificate to connect and asked if you want to remember it for the future.