Skip to content
  • English Deutsch

benjamin-abt.com

Performance is a feature | Cloud - AI - Azure - .NET - Sustainability

  • Home
  • Blog
  • Portfolio
    • Overview
    • Workshops & Training
    • Publications
  • Contact
  • Home
  • Blog

Create Self-Signed HTTPS Certificates with a Custom Root CA

Create Self-Signed HTTPS Certificates with a Custom Root CA
October 15, 2025 7 min read BEN ABT
Security Certificates SSL TLS HTTPS Smart Home OpenSSL

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:

PowerShell
1# Windows PowerShell
2New-Item -ItemType Directory -Path ".\nas.abt" -Force
Bash
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):

INI
 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:

PowerShell
openssl 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:

PowerShell
openssl 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:

PowerShell
openssl 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:

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

Linux/macOS:

Bash
cat 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

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

Expected output:

nas.abt\nas.abt.crt: OK

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

Inspect Certificate Details

PowerShell
openssl 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:

PowerShell
openssl 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!

Related articles

Create a Custom Root Certificate Authority for Self-Signed Certificates

Oct 13, 2025 - 7 min read

Create a Custom Root Certificate Authority for Self-Signed Certificates

When running multiple services in a local network-whether for smart home automation, home labs or development environments-HTTPS encryption …

Self-Hosting Vaultwarden (Bitwarden) with Docker on Synology NAS

Oct 22, 2025 - 19 min read

Self-Hosting Vaultwarden (Bitwarden) with Docker on Synology NAS

You can find the German version of this guide here: Vaultwarden auf Synology mit Docker selbst hosten Password manager services have become …

Vaultwarden (Bitwarden) mit Docker auf Synology NAS selbst hosten

Oct 22, 2025 - 18 min read

Vaultwarden (Bitwarden) mit Docker auf Synology NAS selbst hosten

You can find the English version of this guide here: Self-Hosting Vaultwarden (Bitwarden) on Synology NAS with Docker …


BEN ABT

BEN ABT

Ben is Chief Engineering Officer and Principal Software Engineer at Medialesson.de, with a strong focus on .NET and Microsoft Azure. In his professional role, he designs and builds highly scalable platforms for hybrid and cloud-based scenarios. He also advises companies and C-levels on migrating to the sovereign cloud.

He runs myCSharp.de, the largest and most active German-speaking C# community forum, actively contributes to both his own and external open-source projects and regularly shares his knowledge through his blog, at community events, conferences and as a writer for technical magazines.

Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms

Modernization

Training & Consulting

Contact Me
X Facebook LinkedIn WhatsApp
Back Next Blog Post

Open for work!

Contact Me
BEN ABT BEN ABT Software Engineer · Microsoft MVP Passionate about .NET, Azure, performance optimization and sustainable software engineering.

Medialesson GmbH Logo

Microsoft MVP Logo

Recent posts

View all
Stop Using nginx as Your Default Static File Server in Docker

Stop Using nginx as Your Default Static File Server in …

Oct 24, 2026
Containerizing .NET in 2026 Without a Dockerfile

Containerizing .NET in 2026 Without a Dockerfile

Oct 20, 2026
Problem Details in ASP.NET Core and .NET 11

Problem Details in ASP.NET Core and .NET 11

Oct 5, 2026
Resilience in .NET with Microsoft.Extensions.Resilience

Resilience in .NET with Microsoft.Extensions.Resilience

Sep 21, 2026
What Matters in ASP.NET Core 11

What Matters in ASP.NET Core 11

Sep 14, 2026

Tags

.NET (192) Csharp (96) ASP.NET Core (48) Azure (41) DevOps (17) Docker (14) Microsoft (14) EF Core (11) Microsoft MVP (11) AI (10) GitHub (10) GitHub Copilot (10) ASP.NET (9) Security (9) Synology (9) Architecture (8) Raspberry (8) Azure Functions (7) Cloudflare (7) Bicep (6)
benjamin-abt.com

About BEN

Benjamin Abt is a Software Engineer, Platform Architect and Microsoft MVP specializing in .NET, Azure, cloud architecture, performance optimization and sustainable software development.

Connect

  • Follow me on GitHub
  • Follow me on LinkedIn
  • Follow me on Mastodon
  • Microsoft MVP profile
  • Follow SchwabenCode on GitHub
  • Follow me on X

Quick Links

  • Portfolio
  • Blog
  • Workshops & Training
  • Publications
  • Get in Touch
  • Sitemap
Open for Work

© Copyright Benjamin Abt & SchwabenCode | 2011 - 2026

Content licensed under CC BY 4.0 unless otherwise noted.

  • Impressum
  • Privacy
  • Contact