Create Self-Signed HTTPS Certificates with a Custom Root CA

Create Self-Signed HTTPS Certificates with a Custom Root CA

Once you’ve established your own Root Certificate Authority, the next step is issuing server certificates for your local services. This guide walks through creating HTTPS certificates that are automatically trusted by all devices where your Root CA is installed.

Prerequisites: This post assumes you already have a custom Root CA set up. If you haven’t created one yet, check out my guide on creating a custom Root Certificate Authority .

Why Issue Server Certificates?

Server certificates enable HTTPS encryption for individual services in your network. Each service (Home Assistant, nas, NAS, etc.) gets its own certificate signed by your Root CA. Once the Root CA is trusted on your devices, all server certificates you issue are automatically trusted - no browser warnings.

Benefits:

  • Encrypted communication between clients and servers
  • No browser warnings (“Your connection is not private”)
  • Professional setup even in private networks
  • Service isolation – each service has its own certificate
  • Easy renewal – regenerate certificates as needed

Example Use Case: nas.abt

For this example, I’ll create a certificate for a local NAS running at nas.abt in my local network. The same steps apply to any other service.

Step 1: Create Directory Structure

Organize your certificates by creating a dedicated directory for each service:

1# Windows PowerShell
2New-Item -ItemType Directory -Path ".\nas.abt" -Force
1# Linux/macOS
2mkdir -p nas.abt

Step 2: Create OpenSSL Configuration

Create nas.abt/nas.abt.cnf with the following content. This defines the certificate properties and Subject Alternative Names (SANs):

 1[ req ]
 2default_bits = 2048
 3prompt = no
 4default_md = sha256
 5distinguished_name = dn
 6req_extensions = v3_req
 7
 8[ dn ]
 9C  = DE
10O  = ABT
11CN = nas.abt
12
13[ v3_req ]
14subjectAltName = @alt_names
15
16[ alt_names ]
17DNS.1 = nas.abt
18DNS.2 = nas
19IP.1 = 192.168.1.50

Configuration breakdown:

  • default_bits = 2048: 2048-bit RSA key (standard for server certificates)
  • CN = nas.abt: Common Name matching your service’s domain
  • subjectAltName: Alternative names the certificate is valid for (critical for modern browsers)

Subject Alternative Names (SAN):

Modern browsers (Chrome, Firefox, Safari) require SAN entries. Even if you have only one domain, include it in the SAN list. You can add multiple DNS names and IP addresses:

  • DNS.1 = nas.abt – Primary domain
  • IP.1 = 192.168.1.50 – Direct IP access of my NAS (optional but convenient)

Step 3: Generate Private Key

Create a private key for the server:

1openssl genrsa -out .\nas.abt\nas.abt.key 2048

This generates a 2048-bit RSA private key. Unlike the Root CA key, server keys typically don’t need passphrase protection since they must be loaded automatically by services.

Step 4: Create Certificate Signing Request (CSR)

Generate a CSR using the private key and configuration file:

1openssl req -new -key .\nas.abt\nas.abt.key -out .\nas.abt\nas.abt.csr -config .\nas.abt\nas.abt.cnf

The CSR contains the public key and certificate details. It will be signed by your Root CA in the next step.

Step 5: Sign the Certificate with Your Root CA

Sign the CSR to create the final server certificate:

1openssl x509 -req -in .\nas.abt\nas.abt.csr -CA .\abt-ca\abt-ca.pem -CAkey .\abt-ca\abt-ca.key -CAcreateserial -out .\nas.abt\nas.abt.crt -days 397 -sha256 -extfile .\nas.abt\nas.abt.cnf -extensions v3_req

Command breakdown:

  • -req -in nas.abt.csr: Process the certificate signing request
  • -CA abt-ca/abt-ca.pem: Root CA certificate
  • -CAkey abt-ca/abt-ca.key: Root CA private key
  • -CAcreateserial: Create serial number file (or increment existing)
  • -out nas.abt.crt: Output server certificate
  • -days 397: Certificate validity period
  • -sha256: Use SHA-256 signature algorithm
  • -extfile nas.abt.cnf -extensions v3_req: Include SAN extensions

Important: 397-Day Limit

Why 397 days? In 2020, Apple introduced a maximum certificate validity of 398 days for TLS/SSL certificates in Safari and iOS. Certificates valid longer than this are rejected by Apple platforms, even if signed by a trusted CA.

Best practice: Set server certificates to 397 days (one day less than the limit) to ensure compatibility across all platforms.

For longer validity periods, you must renew certificates regularly. This is actually a security best practice - shorter-lived certificates reduce the impact of key compromise.

Step 6: Create Full Certificate Chain

Some applications require the full certificate chain (server certificate + root CA certificate) in a single file:

1Get-Content .\nas.abt\nas.abt.crt, .\abt-ca\abt-ca.pem | Set-Content .\nas.abt\nas.abt-fullchain.crt -Encoding ascii

Linux/macOS:

1cat nas.abt/nas.abt.crt abt-ca/abt-ca.pem > nas.abt/nas.abt-fullchain.crt

This creates a full chain file containing:

  1. Server certificate (nas.abt.crt)
  2. Root CA certificate (abt-ca.pem)

When to use full chain:

  • nginx with ssl_certificate directive
  • Some Java applications
  • Docker containers expecting chain files
  • Mobile apps with strict certificate validation

Step 7: Verify the Certificate

Before deploying, verify the certificate is correctly signed and contains the expected information.

Verify Certificate Signature

1openssl verify -CAfile .\abt-ca\abt-ca.pem .\nas.abt\nas.abt.crt

Expected output:

1nas.abt\nas.abt.crt: OK

If you see “OK”, the certificate is correctly signed by your Root CA.

Inspect Certificate Details

1openssl x509 -in .\nas.abt\nas.abt.crt -noout -subject -issuer -dates -ext subjectAltName

Expected output:

1subject=C = DE, O = ABT, CN = nas.abt
2issuer=C = DE, O = ABT, CN = ABT Private Root CA
3notBefore=Oct 15 10:00:00 2025 GMT
4notAfter=Nov 15 10:00:00 2026 GMT
5X509v3 Subject Alternative Name:
6    DNS:nas.abt, DNS:nas, IP Address:192.168.1.50

Verify:

  • subject matches your service domain
  • issuer is your Root CA
  • notAfter is approximately 397 days from now
  • Subject Alternative Name includes all expected DNS names and IPs

Using the Certificate

You now have the following files:

  • nas.abt.key – Private key (keep secure!)
  • nas.abt.crt – Server certificate
  • nas.abt-fullchain.crt – Full certificate chain
  • nas.abt.csr – Certificate signing request (can be deleted or archived)

Browser Test

Once installed, test in a browser:

  1. Navigate to https://nas.abt:8200
  2. Verify the lock icon appears (no warnings)
  3. Click the lock icon to view certificate details
  4. Confirm it’s issued by “ABT Private Root CA”

If the Root CA is properly installed, you should see a successful HTTPS connection without certificate errors.

OpenSSL Test

Test the certificate and connection:

1openssl s_client -connect nas.abt:8200 -CAfile .\abt-ca\abt-ca.pem

Look for Verify return code: 0 (ok) in the output.

Renewing Certificates

Certificates expire after 397 days. Plan for renewal before expiration.

Renewal Process

  1. Generate a new CSR (or reuse the old configuration)
  2. Sign with your Root CA (increment the serial number)
  3. Replace the old certificate file
  4. Restart the service

Automation tip: Keep a spreadsheet or calendar with certificate expiration dates. Set reminders 30 days before expiry.

Common Issues

“NET::ERR_CERT_AUTHORITY_INVALID” – Root CA not installed on the client device, or not installed in the correct certificate store.

“NET::ERR_CERT_COMMON_NAME_INVALID” – Domain in the browser doesn’t match any SAN entry. Ensure you access the service using a name listed in alt_names.

Certificate shows as expired immediately – System clock mismatch, or certificate dates are incorrect. Check notBefore and notAfter dates.

Mixed content warnings – HTTPS page loading HTTP resources. Ensure all assets, scripts, and stylesheets use HTTPS or relative URLs.

Service won’t start – Check file permissions on the private key. Most services require the key to be readable only by the service user.

iOS/Safari rejects certificate – Certificate validity exceeds 398 days. Regenerate with -days 397 or less.

Security Best Practices

Protect Private Keys: Store server private keys with restrictive permissions (chmod 600 on Linux, limited NTFS permissions on Windows).

Separate Keys per Service: Never reuse the same private key across multiple services.

Monitor Expiration: Track certificate expiration dates and renew before expiry.

Secure Root CA Access: The Root CA private key should be kept offline when not signing certificates.

Use Strong Ciphers: Configure services to use modern TLS protocols (TLSv1.2, TLSv1.3) and strong cipher suites.

Regular Rotation: Even before expiration, consider rotating certificates periodically (every 6–12 months).

Lessons Learned

  • Always include SAN entries: Modern browsers require Subject Alternative Names, even for single-domain certificates.
  • Respect the 397-day limit: Apple’s policy is enforced strictly. Longer certificates won’t work on iOS/macOS.
  • Keep configuration files: Save .cnf files for each service to simplify renewal.
  • Test on all platforms: Verify certificates work on Windows, macOS, Linux, iOS, and Android before deployment.
  • Use full chains when in doubt: Many applications expect the full certificate chain, not just the server certificate.
  • Document everything: Maintain a certificate inventory with issuance dates, domains, and expiration dates.

Good luck securing your local network!


Comments

Twitter Facebook LinkedIn WhatsApp