Solving HTTPS Challenges: Nginx, Let's Encrypt, & Cloudflare Deep Dive
Brief Summary
This post details setting up and troubleshooting HTTPS for a multi-domain Nginx server. It addresses redirect loops and 403 Forbidden errors encountered when integrating Let’s Encrypt SSL certificates with Cloudflare. Key areas include Nginx configuration patterns, certificate path corrections, static site serving, and proxying for services like n8n and an IPAM tool.
Initial Setup and Objective
The objective was to secure blankhandle.dev
and its subdomains (n8n.blankhandle.dev
, ipam.blankhandle.dev
) with HTTPS. The setup involved:
- A static Hugo journal site.
- An n8n automation instance.
- An IP Address Management (IPAM) tool.
- Cloudflare for DNS and proxying.
- Let’s Encrypt for SSL certificates.
Problems Encountered
Two primary issues arose post-SSL setup:
- “Too Many Redirects” Error: Occurred when accessing
blankhandle.dev
via HTTPS, indicating a redirect loop. - 403 Forbidden Error: The static Hugo site returned a 403 error, despite correct file permissions. These issues suggested misconfigurations in Nginx and its interaction with Cloudflare.
Step-by-Step Solution
Step 1: Initial SSL Certificate Acquisition with Certbot
Certbot’s Nginx plugin was used to obtain and configure SSL certificates:
sudo certbot --nginx -d blankhandle.dev -d www.blankhandle.dev -d n8n.blankhandle.dev -d ipam.blankhandle.dev
While Certbot acquired certificates, its initial Nginx configurations often led to redirect loops, likely due to conflicting directives or insufficient awareness of proxy headers (e.g., X-Forwarded-Proto
).
Step 2: Nginx Configuration Overhaul for Main Domain (blankhandle.dev
)
To resolve the redirect loop, a revised Nginx configuration was applied to /etc/nginx/sites-available/blankhandle.dev.conf
, separating HTTP and HTTPS handling and accounting for Cloudflare’s proxying:
# --- HTTP Block: Redirects all HTTP traffic to HTTPS ---
server {
listen 80;
listen [::]:80;
server_name blankhandle.dev www.blankhandle.dev;
# Redirect all HTTP requests to HTTPS
return 301 https://$host$request_uri;
}
# --- HTTPS Block: Serves the Hugo website securely ---
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name blankhandle.dev www.blankhandle.dev;
# Let's Encrypt SSL certificate paths (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/blankhandle.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blankhandle.dev/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Root directory for the Hugo site's public folder
root /path/to/your/hugo/site/public; # IMPORTANT: Replace with actual path (e.g., /home/username/my-progress-journal/public)
index index.html;
location / {
# Handle X-Forwarded-Proto for Cloudflare integration
# Prevents redirect loops if Cloudflare sends HTTP to port 443 (e.g., "Flexible" SSL)
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
try_files $uri $uri/ =404; # Serve files or return 404
}
# Optional: Custom error pages
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html; # Default Nginx error page location
internal;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html; # Default Nginx error page location
internal;
}
}
Key Nginx Configuration Elements:
- Separated Server Blocks: Distinct handling for HTTP (port 80) and HTTPS (port 443).
ssl_certificate
Paths: Correct paths to Let’s Encrypt certificates.root
Directive: Pointing to the Hugopublic
directory (e.g.,/home/username/my-progress-journal/public
).if ($http_x_forwarded_proto = "http")
: Crucial for Cloudflare integration to prevent redirect loops.
Step 3: Correcting Certificate Paths for Subdomains
Certbot-generated configurations for subdomains (e.g., n8n.blankhandle.dev
, ipam.blankhandle.dev
) initially referenced incorrect, subdomain-specific certificate paths. Certbot typically issues a single certificate covering all specified SANs, stored under the primary domain’s directory.
Updated ssl_certificate
and ssl_certificate_key
paths in n8n.blankhandle.dev.conf
and ipam.blankhandle.dev.conf
to use the primary domain’s certificate:
ssl_certificate /etc/letsencrypt/live/blankhandle.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blankhandle.dev/privkey.pem;
This ensured Nginx used the correct certificate files for all subdomains.
Step 4: Ensuring Proper Site Root for Hugo
The 403 Forbidden error on the static site occurred because Nginx was defaulting to /var/www/html/
instead of the Hugo site’s build output directory. The deploy-hugo.sh
script places generated files into /home/username/my-progress-journal/public/
. The root
directive in the Nginx HTTPS server block (Step 2) was critical to fix this.