E-NO Logo
EN FR
TLS certificates local lab 7 Min Read

TLS certificates local lab setup with practical examples: practical implementation guide

calendar_today Published: 2026-07-22
update Last Updated: 2026-07-22
analytics SEO Efficiency: 97%
Technical guide illustration for TLS certificates local lab setup with practical examples: practical implementation guide.

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 certs)
  • See how to extend the same artifacts to 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:

  1. Name your local endpoints
  • Choose hostnames that never resolve publicly, for example:
  • app.local, api.local
  • Map them to 127.0.0.1 in your hosts file:
  • 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 cert

Option A: OpenSSL (works everywhere)

# 1) Create a root CA (private!)
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"

# 2) Create a server key and CSR for app.local
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"

# 3) Define SANs and usage for the server cert
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

# 4) Sign the server CSR with your CA
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)

# Initialize local trust store for mkcert
mkcert -install

# Issue a cert that covers your names and loopback
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)

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
  1. Optional: enable mutual TLS (client certs)
# Create a client key and CSR
openssl genrsa -out certs/client.key 2048
openssl req -new -key certs/client.key -out certs/client.csr -subj "/CN=lab-client"

# Minimal client cert extensions
cat > certs/client.ext <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF

# Sign client cert
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

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

  1. Extend to Kubernetes Ingress (optional)

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
  1. 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
  1. 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.

Article Quality Score

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