6.7 KiB
6.7 KiB
HTTPS Setup Guide for Container Deployment
Understanding the SSL Error
The net::ERR_SSL_PROTOCOL_ERROR occurs because:
- Frontend tries to make HTTPS requests to the backend
- Backend container only serves HTTP (port 5000)
- SSL termination should happen at the reverse proxy, not individual containers
Correct Architecture
Internet (HTTPS) → Reverse Proxy (SSL Termination) → Containers (HTTP)
- External traffic: HTTPS to reverse proxy
- Internal traffic: HTTP between containers
- SSL termination: Handled by reverse proxy (nginx/traefik)
Solution Options
Option 1: Nginx Reverse Proxy with SSL
1. Create nginx configuration
Create /etc/nginx/sites-available/recipe-app:
server {
listen 80;
server_name your-domain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# SSL Security Settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Frontend (React App)
location / {
proxy_pass http://localhost:3000;
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;
}
# Backend API
location /api/ {
proxy_pass http://localhost:5000/api/;
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;
# CORS headers (if needed)
add_header Access-Control-Allow-Origin "https://your-domain.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
if ($request_method = 'OPTIONS') {
return 204;
}
}
}
2. Enable the site
sudo ln -s /etc/nginx/sites-available/recipe-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
3. Get SSL certificate with Let's Encrypt
sudo certbot --nginx -d your-domain.com
Option 2: Docker Compose with Nginx Container
Create an nginx container in your docker-compose.yml:
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: scoffer-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- frontend
- backend
networks:
- recipe-network
Option 3: Traefik (Automatic SSL)
Add traefik to your docker-compose.yml:
# Traefik Reverse Proxy
traefik:
image: traefik:v2.10
container_name: scoffer-traefik
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=your-email@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-ssl-certs:/letsencrypt
networks:
- recipe-network
# Update frontend service
frontend:
# ... existing config ...
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(\`your-domain.com\`)"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.frontend.loadbalancer.server.port=80"
# Update backend service
backend:
# ... existing config ...
labels:
- "traefik.enable=true"
- "traefik.http.routers.backend.rule=Host(\`your-domain.com\`) && PathPrefix(\`/api\`)"
- "traefik.http.routers.backend.entrypoints=websecure"
- "traefik.http.routers.backend.tls.certresolver=letsencrypt"
- "traefik.http.services.backend.loadbalancer.server.port=5000"
volumes:
traefik-ssl-certs:
Environment Configuration
For Production (.env)
# Frontend points to EXTERNAL HTTPS URL
REACT_APP_API_URL=https://your-domain.com/api
# Backend and DB use internal container names (HTTP)
MONGODB_HOST=mongodb
MONGODB_PORT=27017
JWT_SECRET=your-production-secret
For Local Testing (.env.local)
# Frontend points to localhost (HTTP)
REACT_APP_API_URL=http://localhost:5000/api
# Backend and DB use container names
MONGODB_HOST=mongodb
MONGODB_PORT=27017
JWT_SECRET=your-dev-secret
Troubleshooting
Still getting SSL errors?
-
Check your .env file:
cat .env | grep REACT_APP_API_URL # Should show: REACT_APP_API_URL=https://your-domain.com/api -
Verify reverse proxy is working:
curl -I https://your-domain.com/api/health # Should return 200 OK -
Check container logs:
docker-compose logs frontend docker-compose logs backend docker-compose logs nginx # if using nginx container -
Test internal container communication:
docker-compose exec frontend ping backend docker-compose exec backend ping mongodb
Common Issues
- Mixed content errors: Ensure all API calls use HTTPS in production
- CORS errors: Configure CORS headers in nginx or backend
- Certificate issues: Verify SSL certificate is valid and not expired
- Port conflicts: Ensure ports 80/443 are not used by other services
Security Best Practices
- Use strong SSL configuration (TLS 1.2+)
- Enable HSTS headers
- Configure proper CORS policies
- Use environment-specific secrets
- Regular certificate renewal (certbot auto-renewal)
Quick Fix Summary
- Set up reverse proxy (nginx/traefik) with SSL termination
- Point frontend to HTTPS domain:
REACT_APP_API_URL=https://your-domain.com/api - Keep container communication as HTTP: containers talk to each other via HTTP
- SSL happens at proxy level: not at individual containers