E-NO
TLS certificates local lab 7 Min Read

TLS Certificates Local Lab Setup with Practical Examples

calendar_today Published: 2026-07-22
update Last Updated: 2026-08-06
analytics SEO Efficiency: 97%
Technical guide illustration for TLS Certificates Local Lab Setup with Practical Examples.

Intro

A local TLS lab lets you practice certificate generation, server configuration, and debugging without risking production. In this guide you will:

  • Create a local certificate authority (CA)
  • Issue a server certificate for app.local (127.0.0.1)
  • Configure an Nginx reverse proxy with TLS and secure defaults
  • Validate with curl and openssl
  • Optionally add mutual TLS (client certificates)
  • See how to extend the same artifacts to a Kubernetes Ingress

The focus is a narrow, testable setup you can fully inspect on your machine. Keep it local, keep it simple, and add complexity only after you have reliable checks.

Workflow Overview

A clear, staged workflow reduces rework and speeds up learning:

Choose hostnames that never resolve publicly, for example app.local and api.local. Map them to 127.0.0.1 in your hosts file:

  1. Name your local endpoints
  • Linux/macOS: /etc/hosts
  • Windows: C:\Windows\System32\drivers\etc\hosts

Example entries:

   127.0.0.1 app.local
   127.0.0.1 api.local

You have two common options. Pick one.

  1. Create a local CA and issue a server certificate

Option A: OpenSSL (works everywhere)

   mkdir -p certs
   openssl genrsa -out certs/rootCA.key 4096
   openssl req -x509 -new -nodes -key certs/rootCA.key -sha256 -days 3650 -out certs/rootCA.crt -subj "/CN=lab-root-ca"
   openssl genrsa -out certs/app.local.key 2048
   openssl req -new -key certs/app.local.key -out certs/app.local.csr -subj "/CN=app.local"
   cat > certs/app.local.ext <<'EOF'
   subjectAltName = @alt_names
   basicConstraints = CA:FALSE
   keyUsage = digitalSignature, keyEncipherment
   extendedKeyUsage = serverAuth
   [alt_names]
   DNS.1 = app.local
   DNS.2 = localhost
   IP.1 = 127.0.0.1
   EOF
   openssl x509 -req -in certs/app.local.csr -CA certs/rootCA.crt -CAkey certs/rootCA.key -CAcreateserial -out certs/app.local.crt -days 825 -sha256 -extfile certs/app.local.ext

Option B: mkcert (convenient local CA)

   mkcert -install
   mkcert -cert-file certs/app.local.crt -key-file certs/app.local.key app.local 127.0.0.1 localhost

mkcert also creates its root CA (location varies by OS) and adds it to the system trust store.

Create a minimal Nginx server that terminates TLS and serves a simple response. Adjust paths as needed.

  1. Configure Nginx with TLS

nginx.conf (or a site file you include):

   server {
       listen 443 ssl http2;
       server_name app.local;

       ssl_certificate /absolute/path/certs/app.local.crt;
       ssl_certificate_key /absolute/path/certs/app.local.key;

       ssl_protocols TLSv1.2 TLSv1.3;
       ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
       ssl_prefer_server_ciphers on;
       ssl_session_cache shared:SSL:10m;
       ssl_session_timeout 1h;

       # HSTS for lab: safe if you only use app.local locally
       add_header Strict-Transport-Security "max-age=31536000" always;

       location / {
           return 200 'hello tls lab\n';
           add_header Content-Type text/plain;
       }
   }

   server {
       listen 80;
       server_name app.local;
       return 301 https://$host$request_uri;
   }

Reload Nginx after testing the config:

   nginx -t && nginx -s reload
   # or with systemd
   # sudo systemctl reload nginx

You can trust your root CA system-wide (optional) or point tools to it explicitly.

  1. Validate with curl and OpenSSL

System trust (optional; requires admin privileges):

  • macOS:
     sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain certs/rootCA.crt
  • Debian/Ubuntu:
     sudo cp certs/rootCA.crt /usr/local/share/ca-certificates/lab-root-ca.crt
     sudo update-ca-certificates
  • Windows (Run as Administrator in cmd or PowerShell):
     certutil -addstore -f "Root" certs\rootCA.crt

Direct validation without system trust:

   # Use hosts file and CA explicitly
   curl -vkI https://app.local --cacert certs/rootCA.crt

   # Inspect the served certificate
   openssl s_client -connect app.local:443 -servername app.local -showcerts < /dev/null | openssl x509 -noout -subject -issuer -dates

Optional: enable mutual TLS (client certs)

Create a client certificate signed by the same CA:

openssl genrsa -out certs/client.key 2048
openssl req -new -key certs/client.key -out certs/client.csr -subj "/CN=lab-client"
cat > certs/client.ext <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF
openssl x509 -req -in certs/client.csr -CA certs/rootCA.crt -CAkey certs/rootCA.key -CAcreateserial -out certs/client.crt -days 365 -sha256 -extfile certs/client.ext

Nginx mTLS snippet:

ssl_client_certificate /absolute/path/certs/rootCA.crt;
ssl_verify_client optional; # or 'on' to require all requests

location /secure/ {
    if ($ssl_client_verify != SUCCESS) { return 403; }
    return 200 'hello mtls client\n';
    add_header Content-Type text/plain;
}

Test mTLS:

# Without client cert: expect 403 for /secure/
curl -vk https://app.local/secure/ --cacert certs/rootCA.crt

# With client cert: expect 200
curl -vk https://app.local/secure/ --cacert certs/rootCA.crt --cert certs/client.crt --key certs/client.key

Extend to Kubernetes Ingress (optional)

If you already run a local cluster (kind, minikube, etc.) with an Ingress controller, reuse the same cert and key.

Create a TLS secret:

kubectl create namespace demo
kubectl -n demo create secret tls app-local-tls --cert=certs/app.local.crt --key=certs/app.local.key

Ingress manifest (nginx Ingress example):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-local
  namespace: demo
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - app.local
    secretName: app-local-tls
  rules:
  - host: app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-svc
            port:
              number: 80

Point app.local to the Ingress endpoint (NodePort, LoadBalancer, or port-forward). Example with port-forwarding the controller to localhost:

# Adjust names to your controller service
kubectl -n ingress-nginx port-forward svc/ingress-nginx-controller 8443:443
# Then:
curl -vkI https://app.local:8443 --resolve app.local:8443:127.0.0.1 --cacert certs/rootCA.crt

Rotate certificates safely

  • Issue a new server cert with the same SANs
  • Update file paths atomically (or overwrite the files)
  • Validate Nginx config and reload
openssl x509 -in certs/app.local.crt -noout -enddate
nginx -t && nginx -s reload

Troubleshooting quick wins

  • Hostname mismatch: check SANs with openssl x509 -in certs/app.local.crt -noout -text | grep -A1 "Subject Alternative Name"
  • Wrong chain: confirm issuer with -issuer and ensure the server sends the correct cert
  • Protocol/cipher: curl -v --tls-max 1.2 https://app.local to force-check versions
  • Nginx logs: check error log for handshake or certificate errors

Local Pilot Plan

Goal: one hostname (app.local) over TLS on localhost with measurable checks.

Scope

  • Single server: Nginx on 127.0.0.1
  • One hostname: app.local
  • One CA, one server cert

Steps

  1. Add hosts entry for app.local -> 127.0.0.1
  2. Create a local CA and a server cert (OpenSSL commands above)
  3. Configure Nginx with TLS
  4. Validate with curl and OpenSSL

Success criteria (measurable)

  • curl -skI https://app.local --cacert certs/rootCA.crt returns HTTP/1.1 or HTTP/2 200 OK for the TLS vhost
  • openssl s_client -connect app.local:443 -servername app.local shows a chain with issuer = lab-root-ca and a valid Not After date in the future
  • nginx -t succeeds and nginx -s reload completes without errors

Timebox

  • 45 to 60 minutes end to end, including validation

Optional stretch

  • Add mTLS for /secure/ and verify 403 without a client cert and 200 with one

Next extension after success

  • Add api.local as a second hostname using the same CA and repeat the checks

Conclusion

You built a small, safe TLS lab that you can reuse for testing, troubleshooting, and experiments. You created a local CA, issued server and client certificates, configured Nginx with secure defaults, and validated behavior with curl and OpenSSL. Keep improvements incremental: script cert issuance and rotation, add more hostnames, and optionally reuse the same artifacts in a Kubernetes Ingress. Always define clear, local checks so you can verify each change before moving on.

Related Research

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL