--- name: local-ssl description: Enable HTTPS for local development with trusted SSL certificates. Use when developers need to test SSL/TLS features, work with third-party APIs requiring HTTPS, or simulate production environments. Supports mkcert, OpenSSL, and automatic certificate trust configuration for macOS, Linux, and Windows. --- # Local SSL/HTTPS Enable HTTPS for local development environments with automatically trusted SSL certificates. This skill handles certificate generation, trust configuration, and web server setup. ## Core Workflows ### 1. mkcert Setup (Recommended Approach) mkcert is the simplest way to generate locally-trusted certificates. ```bash # Generate: scripts/setup-ssl.sh #!/bin/bash set -e RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' echo -e "${BLUE}๐Ÿ” Setting up local SSL certificates...${NC}\n" # Check if mkcert is installed if ! command -v mkcert &> /dev/null; then echo -e "${BLUE}๐Ÿ“ฆ Installing mkcert...${NC}" # Detect OS and install mkcert if [[ "$OSTYPE" == "darwin"* ]]; then # macOS brew install mkcert nss # nss for Firefox support elif [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux if command -v apt-get &> /dev/null; then sudo apt-get install -y libnss3-tools curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" chmod +x mkcert-v*-linux-amd64 sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert fi else echo -e "${RED}โŒ Unsupported OS. Please install mkcert manually.${NC}" exit 1 fi fi # Create certs directory mkdir -p certs # Install local CA echo -e "${BLUE}๐Ÿ”’ Installing local Certificate Authority...${NC}" mkcert -install # Generate certificates for localhost and custom domains echo -e "${BLUE}๐Ÿ”‘ Generating certificates...${NC}" mkcert -cert-file certs/localhost.crt \ -key-file certs/localhost.key \ localhost 127.0.0.1 ::1 \ *.localhost \ local.dev \ *.local.dev echo -e "\n${GREEN}โœ… SSL certificates generated!${NC}" echo -e "${BLUE}๐Ÿ“ Certificate files:${NC}" echo -e " certs/localhost.crt" echo -e " certs/localhost.key" ``` ### 2. Docker Compose with SSL ```yaml # Update docker-compose.yml to mount certificates version: '3.8' services: frontend: build: context: ./frontend dockerfile: Dockerfile.dev ports: - "3000:3000" - "3443:3443" # HTTPS port environment: - HTTPS=true - SSL_CRT_FILE=/certs/localhost.crt - SSL_KEY_FILE=/certs/localhost.key volumes: - ./frontend:/app - /app/node_modules - ./certs:/certs:ro # Mount certificates as read-only networks: - app-network api: build: context: ./backend dockerfile: Dockerfile.dev ports: - "8000:8000" - "8443:8443" # HTTPS port environment: - SSL_CERT=/certs/localhost.crt - SSL_KEY=/certs/localhost.key volumes: - ./backend:/app - /app/node_modules - ./certs:/certs:ro networks: - app-network nginx: image: nginx:alpine ports: - "443:443" volumes: - ./nginx/nginx-ssl.conf:/etc/nginx/conf.d/default.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - frontend - api networks: - app-network ``` ### 3. Nginx SSL Configuration ```nginx # Generate: nginx/nginx-ssl.conf # Frontend HTTPS server { listen 443 ssl http2; server_name localhost local.dev; ssl_certificate /etc/nginx/certs/localhost.crt; ssl_certificate_key /etc/nginx/certs/localhost.key; # SSL Configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; location / { proxy_pass http://frontend:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; 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; } } # API HTTPS server { listen 443 ssl http2; server_name api.localhost api.local.dev; ssl_certificate /etc/nginx/certs/localhost.crt; ssl_certificate_key /etc/nginx/certs/localhost.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://api:8000; proxy_http_version 1.1; 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; } } # HTTP to HTTPS redirect server { listen 80; server_name localhost local.dev api.localhost api.local.dev; return 301 https://$host$request_uri; } ``` ### 4. Vite HTTPS Configuration ```typescript // Generate: frontend/vite.config.ts (with HTTPS) import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import fs from 'fs'; import path from 'path'; export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', port: 3000, https: process.env.HTTPS === 'true' ? { key: fs.readFileSync(path.resolve(__dirname, '../certs/localhost.key')), cert: fs.readFileSync(path.resolve(__dirname, '../certs/localhost.crt')), } : undefined, watch: { usePolling: true, }, }, }); ``` ### 5. Node.js/Express HTTPS Setup ```typescript // Generate: backend/src/server.ts (with HTTPS) import express from 'express'; import https from 'https'; import http from 'http'; import fs from 'fs'; import path from 'path'; const app = express(); // Your middleware and routes here app.use(express.json()); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', protocol: req.protocol }); }); const PORT = process.env.PORT || 8000; const HTTPS_PORT = 8443; // Start HTTP server http.createServer(app).listen(PORT, () => { console.log(`HTTP server running on http://localhost:${PORT}`); }); // Start HTTPS server if certificates exist if (process.env.SSL_CERT && process.env.SSL_KEY) { try { const httpsOptions = { key: fs.readFileSync(process.env.SSL_KEY), cert: fs.readFileSync(process.env.SSL_CERT), }; https.createServer(httpsOptions, app).listen(HTTPS_PORT, () => { console.log(`HTTPS server running on https://localhost:${HTTPS_PORT}`); }); } catch (error) { console.error('Failed to start HTTPS server:', error); } } ``` ### 6. OpenSSL Alternative (Manual) For systems where mkcert isn't available: ```bash # Generate: scripts/generate-certs-openssl.sh #!/bin/bash set -e mkdir -p certs # Generate private key openssl genrsa -out certs/localhost.key 2048 # Generate certificate signing request openssl req -new -key certs/localhost.key -out certs/localhost.csr \ -subj "/C=US/ST=State/L=City/O=Development/CN=localhost" # Generate self-signed certificate openssl x509 -req -days 365 -in certs/localhost.csr \ -signkey certs/localhost.key -out certs/localhost.crt \ -extfile <(printf "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1") # Clean up CSR rm certs/localhost.csr echo "โœ… Certificates generated!" echo "โš ๏ธ You'll need to manually trust the certificate in your browser" ``` ### 7. Certificate Trust Scripts ```bash # Generate: scripts/trust-cert.sh #!/bin/bash RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' CERT_FILE="certs/localhost.crt" if [ ! -f "$CERT_FILE" ]; then echo -e "${RED}โŒ Certificate not found: $CERT_FILE${NC}" exit 1 fi echo -e "${BLUE}๐Ÿ”’ Adding certificate to system trust store...${NC}" if [[ "$OSTYPE" == "darwin"* ]]; then # macOS sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain "$CERT_FILE" echo -e "${GREEN}โœ… Certificate trusted on macOS${NC}" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux sudo cp "$CERT_FILE" /usr/local/share/ca-certificates/localhost.crt sudo update-ca-certificates echo -e "${GREEN}โœ… Certificate trusted on Linux${NC}" else echo -e "${RED}โŒ Unsupported OS for automatic trust${NC}" echo "Please manually trust the certificate:" echo " - Chrome: chrome://settings/certificates" echo " - Firefox: about:preferences#privacy" fi ``` ## Environment Configuration ```bash # Add to .env # SSL Configuration HTTPS=true SSL_CERT_FILE=./certs/localhost.crt SSL_KEY_FILE=./certs/localhost.key # Update URLs to use HTTPS VITE_API_URL=https://localhost:8443 API_BASE_URL=https://localhost:8443 ``` ## Testing SSL Setup ```bash # Generate: scripts/test-ssl.sh #!/bin/bash echo "๐Ÿงช Testing SSL setup..." # Test HTTPS endpoint if curl -k -s https://localhost:3443 > /dev/null 2>&1; then echo "โœ… Frontend HTTPS is working" else echo "โŒ Frontend HTTPS failed" fi if curl -k -s https://localhost:8443/health > /dev/null 2>&1; then echo "โœ… API HTTPS is working" else echo "โŒ API HTTPS failed" fi # Check certificate validity echo "" echo "๐Ÿ“‹ Certificate details:" openssl x509 -in certs/localhost.crt -noout -subject -dates ``` ## Makefile Integration ```makefile .PHONY: ssl-setup ssl-test ssl-clean ssl-setup: ## Generate and trust SSL certificates @./scripts/setup-ssl.sh ssl-test: ## Test SSL configuration @./scripts/test-ssl.sh ssl-clean: ## Remove SSL certificates @rm -rf certs @echo "โœ… SSL certificates removed" ``` ## Best Practices 1. **Never commit certificates** - Add `certs/` to .gitignore 2. **Use mkcert for development** - Easiest and most reliable 3. **Automate setup** - Include in onboarding scripts 4. **Test in all browsers** - Chrome, Firefox, Safari, Edge 5. **Document the process** - Help teammates get started quickly 6. **Use consistent domains** - *.localhost or *.local.dev ## Integration with zero-to-running When both skills are active: 1. SSL is set up automatically during `make dev` 2. Services available over HTTPS 3. Certificates automatically trusted 4. Environment variables configured correctly ## Troubleshooting **Certificate not trusted:** ```bash # Reinstall CA mkcert -uninstall mkcert -install ``` **Port conflicts:** ```bash # Change ports in .env HTTPS_PORT=4443 ``` **Browser shows NET::ERR_CERT_AUTHORITY_INVALID:** - Run `mkcert -install` again - Restart browser completely - Check system keychain for certificate