Enabling Local HTTPS for Development and Debugging
Local HTTPS matters because browser APIs like Service Workers require a secure context, and this covers Caddy, Nginx with mkcert, Puma-dev, and Cert-Manager.
· 7 min read
Developing and debugging web applications often requires mirroring production as closely as possible, and that includes HTTPS. HTTP works for basic local testing, but many browser features, APIs, and security behaviors require a secure context. This covers several tools for local HTTPS: Nginx, Caddy, Puma-dev, and a local Kubernetes environment with Cert-Manager.
Why Local HTTPS Matters
Beyond matching production, local HTTPS has concrete benefits:
- Secure-context APIs: Geolocation, Service Workers, WebUSB, and the Payment Request API are restricted to secure contexts. Developing with HTTPS locally means these actually work.
- Mixed content: when parts of an application are served over HTTP and others over HTTPS, browsers block the insecure content, causing broken functionality or visual glitches.
- Cookie security: secure cookies (the
Secureattribute) only get sent over HTTPS, so local HTTPS is the only way to test that path accurately. - HSTS: if production uses HSTS, browsers enforce HTTPS on all later connections. Developing over plain HTTP locally can produce unexpected redirects or errors.
- Realism: testing under the same protocol as production surfaces redirect and certificate issues earlier.
Tools for Local HTTPS
Caddy: Automatic HTTPS
Caddy is a modern, open-source web server known for automatic HTTPS, and it’s easy to set up for local development.
How it works:
Caddy automatically provisions and manages TLS certificates for your local domains using its internal CA (Certificate Authority). The first time it does this, it’ll typically prompt you to install its root certificate into your system’s trust store. Once trusted, your browser will recognize certificates issued by Caddy as valid for your local domains.
Setup:
-
Install Caddy: Refer to the official Caddy documentation for installation instructions specific to your operating system. For macOS,
brew install caddyis common. -
Create a Caddyfile: Create a file named
Caddyfilein your project root or a central location. Here’s a basic example for a local application running onlocalhost:3000:mylocalapp.test { tls internal reverse_proxy localhost:3000 }mylocalapp.test: This is your custom local domain. You’ll need to configure yourhostsfile (or a local DNS resolver likednsmasq) to point this domain to127.0.0.1.tls internal: This tells Caddy to automatically manage self-signed certificates for this domain and try to install its CA into your system’s trust store.reverse_proxy localhost:3000: This forwards incoming HTTPS requests to your application running onlocalhost:3000(or whatever port your app uses).
-
Run Caddy: Navigate to the directory containing your
Caddyfilein your terminal and run:caddy runCaddy will prompt you to trust its root certificate. Accept it.
-
Access your App: Now, you can access your application securely at
https://mylocalapp.test.
Advantages:
- Simplicity: Minimal configuration required for HTTPS.
- Automatic Certificate Management: Caddy handles certificate generation, renewal, and trust store integration.
- HTTP/2 and HTTP/3 support: Modern protocols out of the box.
Nginx with mkcert
Nginx is a powerful and widely used web server and reverse proxy. While it doesn’t have Caddy’s automatic HTTPS magic, you can easily pair it with a tool like mkcert to generate locally trusted certificates.
How it works:
mkcert is a simple tool that creates locally-trusted development certificates. It does this by creating its own local CA and installing it into your system’s trust store. You then use these certificates with Nginx.
Setup:
-
Install mkcert: Follow the installation instructions for mkcert. For macOS,
brew install mkcertandmkcert -installare typical. This command will install a local CA into your system’s trust store. -
Generate Certificates with mkcert: Navigate to your project directory or a dedicated certificates folder and generate certificates for your local domain:
mkcert mylocalapp.test localhost 127.0.0.1This will create
mylocalapp.test+2.pem(certificate) andmylocalapp.test+2-key.pem(private key) files. -
Install Nginx: Install Nginx according to your operating system’s instructions.
-
Configure Nginx: Create or modify your Nginx configuration file (e.g.,
nginx.confor a separate site configuration insites-available/sites-enabled).server { listen 443 ssl; server_name mylocalapp.test; ssl_certificate /path/to/your/certs/mylocalapp.test+2.pem; ssl_certificate_key /path/to/your/certs/mylocalapp.test+2-key.pem; # Optional: Redirect HTTP to HTTPS listen 80; return 301 https://$host$request_uri; location / { proxy_pass http://localhost:3000; # Your application's HTTP address proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }Replace
/path/to/your/certs/with the actual path where you saved your mkcert files. -
Update
hostsfile: Add an entry to yourhostsfile (e.g.,/etc/hostson Linux/macOS,C:\Windows\System32\drivers\etc\hostson Windows):127.0.0.1 mylocalapp.test -
Restart Nginx:
sudo nginx -s reload # or appropriate command for your system -
Access your App: You should now be able to access your application securely at
https://mylocalapp.test.
Advantages:
- Flexibility: Highly configurable for complex setups.
- Performance: Excellent performance for serving static files and reverse proxying.
- Widely Used: Extensive community support and resources.
Puma-dev
For Rails developers, Puma-dev is a streamlined option for local HTTPS with custom domains.
How it works:
Puma-dev acts as a local DNS server and HTTP/HTTPS proxy. It automatically resolves *.test domains (or other configurable TLDs) to your local machine, starts your Rails applications on demand, and provides HTTPS with self-signed certificates that are automatically trusted.
Setup:
-
Install Puma-dev:
gem install puma # Ensure puma gem is installed in your app's Gemfile brew install puma/puma/puma-dev # For macOS sudo puma-dev -setup # Sets up necessary DNS and certificate configurations puma-dev -install # Installs puma-dev as a user agent -
Link your Rails App: Navigate to your Rails application’s root directory and link it:
puma-dev link -n myrailsappThis will create a symlink in
~/.puma-dev(or wherever Puma-dev stores its linked apps). -
Access your App: Now, simply open your browser and navigate to
https://myrailsapp.test. Puma-dev will automatically start your Rails application and serve it over HTTPS.
Advantages:
- Rails-centric: Designed specifically for Ruby on Rails development.
- Zero Configuration: Minimal setup for automatic app linking, custom domains, and HTTPS.
- On-Demand Starting: Only starts your Rails app when accessed, saving resources.
Kubernetes Local Environment with Cert-Manager
For applications intended for Kubernetes, a local cluster (Minikube, Kind) with Cert-Manager gives the closest local match to production HTTPS.
How it works:
Cert-Manager is a native Kubernetes certificate management controller. It can issue certificates from various sources, including self-signed CAs, Vault, or public CAs like Let’s Encrypt. For local development, you’ll typically configure a ClusterIssuer or Issuer that uses a self-signed CA. Cert-Manager then watches Certificate resources and automatically provisions TLS secrets for your Ingresses.
Setup (using Minikube and a self-signed Issuer):
-
Set up a Local Kubernetes Cluster: Install and start Minikube (or Kind):
minikube start -
Install Cert-Manager: Install Cert-Manager using Helm or kubectl manifests. It’s recommended to use Helm:
helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.14.x # Use the latest stable versionVerify the installation:
kubectl get pods -n cert-manager -
Create a Self-Signed ClusterIssuer: This defines a cluster-wide CA that Cert-Manager will use to sign your development certificates. Create
selfsigned-clusterissuer.yaml:apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-issuer spec: selfSigned: {}Apply it:
kubectl apply -f selfsigned-clusterissuer.yaml -
Create an Ingress and Certificate Resource: Assuming you have a deployment and service for your application, you’ll need an
Ingressto expose it and aCertificateresource for Cert-Manager to manage its TLS.Example
my-app-ingress.yaml:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / cert-manager.io/cluster-issuer: selfsigned-issuer # Link to your ClusterIssuer spec: rules: - host: myapp.local.com http: paths: - path: / pathType: Prefix backend: service: name: my-app-service # Your application's service name port: number: 80 tls: # Enable TLS for this Ingress - hosts: - myapp.local.com secretName: my-app-tls-secret # Cert-Manager will create this secret --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: my-app-certificate namespace: default # Or your application's namespace spec: secretName: my-app-tls-secret dnsNames: - myapp.local.com issuerRef: name: selfsigned-issuer kind: ClusterIssuerApply these resources:
kubectl apply -f my-app-ingress.yaml -
Install an Ingress Controller: You’ll need an Ingress Controller (e.g., Nginx Ingress Controller) to actually route traffic. For Minikube, you can enable it with:
minikube addons enable ingress -
Update
hostsfile: Get the Minikube IP and add an entry to yourhostsfile:minikube ip # Example output: 192.168.49.2Add to your
hostsfile:192.168.49.2 myapp.local.com -
Trust the Cert-Manager CA: Cert-Manager will create a CA certificate for its self-signed issuer. You’ll need to extract this and manually trust it in your operating system’s trust store. The exact steps vary, but it typically involves:
- Getting the CA certificate from a Kubernetes secret (look for
cert-manager-caor similar, usually in thecert-managernamespace). - Decoding the base64-encoded certificate.
- Importing it into your system’s trusted root certificates.
Example (might vary):
kubectl get secret -n cert-manager cert-manager-webhook-ca -o jsonpath='{.data.ca\.crt}' | base64 --decode > cert-manager-ca.crtThen, import
cert-manager-ca.crtinto your OS. - Getting the CA certificate from a Kubernetes secret (look for
-
Access your App: Now, you can access your application securely at
https://myapp.local.com.
Advantages:
- Production Parity: Closest to how HTTPS is managed in a production Kubernetes environment.
- Automated Certificate Lifecycle: Cert-Manager handles issuance, renewal, and secret management.
- Scalable: Ideal for complex microservice architectures.
Caddy is the simplest option, Nginx with mkcert gives the most control, Puma-dev fits a Rails-only workflow, and Kubernetes with Cert-Manager is worth the setup only if you’re already testing against a local cluster. Any of them beats debugging secure-context bugs for the first time in production.