Generate a self-signed TLS certificate (Linux / macOS / Windows)
Create a self-signed TLS certificate and private key with OpenSSL for internal homelab services. Includes Subject Alternative Names so modern browsers accept it.
bash# Works on Linux, macOS, and Windows (with OpenSSL installed)
# Replace the values below with your own hostname, IP, and organisation
openssl req -x509 \
-newkey rsa:4096 \
-sha256 \
-days 3650 \
-nodes \
-keyout server.key \
-out server.crt \
-subj "/CN=homelab.lan/O=Homelab/C=US" \
-addext "subjectAltName=DNS:homelab.lan,DNS:*.homelab.lan,IP:192.168.1.1"
What this does
Generates two files in the current directory:
server.key— the RSA private key (keep this secret)server.crt— the self-signed X.509 certificate (valid 10 years)
The -addext subjectAltName flag is critical — modern browsers (Chrome 58+, Firefox, Safari) reject certificates that only have a CN and no Subject Alternative Names (SAN). Without a SAN, you will get a NET::ERR_CERT_COMMON_NAME_INVALID error regardless of trusting the cert.
Prerequisites
OpenSSL is required on all platforms.
Linux: Usually pre-installed. If not:
sudo apt-get install -y openssl # Ubuntu / Debian
sudo dnf install -y openssl # Fedora / Rocky
macOS: Pre-installed. Verify: openssl version
Windows: Not built-in. Install via one of:
# Option 1: winget
winget install ShiningLight.OpenSSL
# Option 2: Chocolatey
choco install openssl
# Option 3: Git for Windows includes OpenSSL
# Use Git Bash to run the openssl command above
Verify: openssl version (should show 3.x)
Customise for your environment
Edit the -subj and -addext values before running:
| Field | Example | Replace with |
|---|---|---|
CN |
homelab.lan |
Your primary hostname |
O |
Homelab |
Your name or org |
C |
US |
Your 2-letter country code |
DNS:homelab.lan |
homelab.lan |
Hostname(s) that will use this cert |
DNS:*.homelab.lan |
*.homelab.lan |
Wildcard — covers all subdomains |
IP:192.168.1.1 |
192.168.1.1 |
IP address of the service |
Multiple SANs — add as many as needed, comma-separated:
-addext "subjectAltName=DNS:proxmox.homelab.lan,DNS:grafana.homelab.lan,IP:192.168.1.10,IP:192.168.1.68"
Inspect the certificate
# View the certificate contents (confirm SANs are present)
openssl x509 -in server.crt -text -noout | grep -A5 "Subject Alternative"
# Show expiry date
openssl x509 -in server.crt -noout -enddate
# Verify the key matches the certificate
openssl x509 -noout -modulus -in server.crt | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
# Both lines must produce the same hash
Trust the certificate on each client
Self-signed certs will show a browser warning until you add them to your system’s trust store.
Linux (Ubuntu / Debian):
sudo cp server.crt /usr/local/share/ca-certificates/homelab.crt
sudo update-ca-certificates
Linux (Fedora / Rocky / openSUSE):
sudo cp server.crt /etc/pki/ca-trust/source/anchors/homelab.crt
sudo update-ca-trust
macOS:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain server.crt
Windows (PowerShell, run as Administrator):
Import-Certificate -FilePath "server.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
After adding to the trust store, restart your browser.
Install on common homelab services
Nginx:
server {
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
}
Proxmox (replaces the default self-signed cert):
cp server.crt /etc/pve/local/pve-ssl.pem
cp server.key /etc/pve/local/pve-ssl.key
systemctl restart pveproxy
Docker / Portainer: mount server.crt and server.key into the container and point the service’s TLS config at them.
Notes
-nodesmeans “no DES” — the private key is not encrypted with a passphrase. For a service certificate this is normal (the service needs to read the key at startup without human input). Store the key file withchmod 600and restrict access- The 10-year validity (
-days 3650) is practical for homelab use; production certificates should use 1 year or less - For a proper internal CA (sign multiple service certs from one trusted root), generate a CA key/cert separately and use
openssl cato sign — one root cert to trust across all clients instead of trusting each service cert individually