HTTPS on EC2: Free SSL with Let's Encrypt, Certbot & Nginx
Your EC2-hosted site is live, but browsers are stamping it with a red 'Not Secure' warning — a trust-killer for users and a ranking penalty from search engines. This guide walks you through obtaining a free, auto-renewing SSL/TLS certificate from Let's Encrypt using Certbot and wiring it into Nginx on an Amazon EC2 instance running Ubuntu.
TL;DR — What You'll Do
| Step | Action | Outcome |
|---|---|---|
| 1 | Open ports 80 & 443 in Security Group | EC2 accepts HTTP/HTTPS traffic |
| 2 | Point your domain's A record to EC2 Elastic IP | Domain resolves to your instance |
| 3 | Install Nginx | Web server ready to serve requests |
| 4 | Install Certbot + Nginx plugin | Certificate toolchain in place |
| 5 | Run certbot --nginx | Certificate issued & Nginx auto-configured |
| 6 | Verify auto-renewal | Certificate renews every 60–90 days automatically |
Architecture Overview
Before touching the terminal, understand the full request path from browser to your application:
(Inbound: 80, 443)"] SG --> Nginx["Nginx
(TLS Termination)"] Nginx -->|"HTTP proxy"| App["Your App
(e.g., Node.js :3000)"] Nginx -.->|"Certificate files"| Cert["/etc/letsencrypt/live/
fullchain.pem + privkey.pem"] LE["Let's Encrypt CA"] -.->|"Issues cert via Certbot"| Cert Browser -->|"TCP 80 (redirect)"| SG
- Browser initiates a TLS handshake on port 443 to your EC2 Elastic IP.
- AWS Security Group acts as the first gate — it must allow inbound TCP 443 (and 80 for HTTP→HTTPS redirect).
- Nginx terminates TLS using the Let's Encrypt certificate stored on disk, then proxies the decrypted request to your app (e.g., Node.js on port 3000).
- Let's Encrypt CA is only involved during certificate issuance and renewal — not on every request.
- Certbot's ACME challenge temporarily serves a token over HTTP (port 80) to prove domain ownership during issuance.
Prerequisites
- EC2 instance running Ubuntu 22.04 LTS (steps are similar for 20.04)
- An Elastic IP associated with the instance
- A registered domain name with an A record pointing to that Elastic IP
- SSH access with
sudoprivileges
Analogy: Think of Let's Encrypt as a free notary service. Certbot is your paralegal — it prepares the paperwork, proves you own the property (domain), gets the notary's stamp (certificate), and files it with Nginx. The notary never sits in your office; it only shows up when the stamp needs renewing.
Step 1 — Configure EC2 Security Group
Your Security Group is a stateful firewall at the AWS network layer. Without opening ports 80 and 443, no external traffic reaches Nginx — and Certbot's ACME HTTP-01 challenge will fail silently.
Navigate to EC2 → Security Groups → Inbound Rules → Edit and add:
| Type | Protocol | Port | Source | Purpose |
|---|---|---|---|---|
| HTTP | TCP | 80 | 0.0.0.0/0, ::/0 | ACME challenge + HTTP→HTTPS redirect |
| HTTPS | TCP | 443 | 0.0.0.0/0, ::/0 | Encrypted traffic |
| SSH | TCP | 22 | Your IP only | Admin access (least privilege) |
Or via AWS CLI:
# Replace sg-xxxxxxxxxxxxxxxxx with your Security Group ID
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxxxxxxxxxxxxx \
--protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxxxxxxxxxxxxx \
--protocol tcp --port 443 --cidr 0.0.0.0/0
Step 2 — Assign an Elastic IP (If Not Done)
A dynamic public IP changes on every instance stop/start, breaking your DNS A record. An Elastic IP is a static IPv4 address you own until you release it.
# Allocate a new Elastic IP
aws ec2 allocate-address --domain vpc
# Associate it with your instance (replace with real IDs)
aws ec2 associate-address \
--instance-id i-0123456789abcdef0 \
--allocation-id eipalloc-0123456789abcdef0
Then update your domain registrar's DNS: add an A record pointing yourdomain.com (and www.yourdomain.com) to this Elastic IP. DNS propagation can take up to 48 hours, but typically resolves within minutes.
Step 3 — Install Nginx
sudo apt update && sudo apt upgrade -y
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx
Verify Nginx is running:
sudo systemctl status nginx
# Expected: Active: active (running)
Visiting http://<your-elastic-ip> in a browser should show the default Nginx welcome page.
Step 4 — Create an Nginx Server Block for Your Domain
Certbot needs a server block that references your domain name before it can configure HTTPS. Create a minimal one:
🔽 [Click to expand] /etc/nginx/sites-available/yourdomain.com
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
# Create the web root directory
sudo mkdir -p /var/www/yourdomain.com/html
sudo chown -R $USER:$USER /var/www/yourdomain.com/html
# Enable the site by symlinking to sites-enabled
sudo ln -s /etc/nginx/sites-available/yourdomain.com \
/etc/nginx/sites-enabled/
# Test config syntax
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Step 5 — Install Certbot with the Nginx Plugin
The recommended installation method on Ubuntu 22.04 is via snap, as maintained by the Electronic Frontier Foundation (EFF):
# Remove any OS-packaged certbot to avoid conflicts
sudo apt remove certbot -y
# Install via snap (ensures latest version)
sudo snap install --classic certbot
# Create a symlink so certbot is in PATH
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Step 6 — Obtain and Install the Certificate
This single command handles everything: domain validation via HTTP-01 ACME challenge, certificate issuance, and Nginx configuration update.
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will prompt you for:
- An email address for expiry notifications
- Agreement to the Let's Encrypt Terms of Service
- Whether to share your email with EFF (optional)
- Whether to redirect HTTP to HTTPS (choose 2: Redirect — always recommended)
On success, Certbot modifies your Nginx server block to add SSL directives and creates a redirect from port 80 to 443.
What Certbot Writes to Your Nginx Config
After running Certbot, your server block will look similar to this:
🔽 [Click to expand] Modified /etc/nginx/sites-available/yourdomain.com
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Certbot adds this redirect block:
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com/html;
index index.html;
# Managed by Certbot
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ =404;
}
}
Step 7 — Verify Auto-Renewal
Let's Encrypt certificates are valid for 90 days. Certbot installs a systemd timer (or cron job) that attempts renewal twice daily when the certificate is within 30 days of expiry.
# Check the renewal timer status
sudo systemctl status snap.certbot.renew.timer
# Perform a dry-run to confirm renewal works without issuing a cert
sudo certbot renew --dry-run
Expected output includes: Congratulations, all simulated renewals succeeded.
Step 8 — (Optional) Reverse Proxy to a Backend App
If Nginx is fronting a Node.js, Python, or Java app running on a local port, update the location / block:
🔽 [Click to expand] Nginx reverse proxy config snippet
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
sudo nginx -t && sudo systemctl reload nginx
Certificate Issuance Flow (Sequence Diagram)
- You run
certbot --nginxon the EC2 instance. - Certbot contacts the Let's Encrypt ACME API and requests a challenge token for your domain.
- Let's Encrypt makes an HTTP request to
http://yourdomain.com/.well-known/acme-challenge/<token>to verify you control the domain. - Certbot temporarily serves the token response via Nginx on port 80.
- Let's Encrypt validates the response and issues the signed certificate.
- Certbot writes the certificate files to
/etc/letsencrypt/live/yourdomain.com/and updates the Nginx config.
Common Errors & Fixes
| Error | Root Cause | Fix |
|---|---|---|
Connection refused on port 80 | Security Group blocks port 80 | Add inbound rule for TCP 80 |
DNS problem: NXDOMAIN | A record not propagated yet | Wait for DNS propagation; verify with dig yourdomain.com |
Too many certificates already issued | Let's Encrypt rate limit hit (5 certs/domain/week) | Use --staging flag for testing; wait for rate limit reset |
nginx: [emerg] unknown directive | Certbot wrote config with syntax error | Run sudo nginx -t to identify line; check Certbot version |
| Certificate not renewing | Port 80 blocked or Nginx down at renewal time | Ensure port 80 stays open; check certbot renew --dry-run |
Security Hardening Checklist
- ✅ Keep port 80 open — required for HTTP-01 renewal challenges (Certbot handles the redirect to HTTPS)
- ✅ Restrict SSH (port 22) to your specific IP in the Security Group
- ✅ Enable UFW on the OS level as a secondary firewall layer:
sudo ufw allow 'Nginx Full' - ✅ Use HSTS — add
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;to your HTTPS server block - ✅ Disable weak TLS versions — Certbot's
options-ssl-nginx.confalready enforces TLS 1.2+ by default - ✅ Never expose private keys —
/etc/letsencrypt/live/is root-only by default; do not change permissions
Glossary
| Term | Definition |
|---|---|
| Let's Encrypt | A free, automated, open Certificate Authority (CA) run by the Internet Security Research Group (ISRG) |
| Certbot | EFF's open-source ACME client that automates certificate issuance and renewal from Let's Encrypt |
| ACME | Automated Certificate Management Environment — the protocol Certbot uses to communicate with Let's Encrypt |
| HTTP-01 Challenge | Domain ownership proof method where Let's Encrypt fetches a token file served over HTTP on port 80 |
| Elastic IP | A static public IPv4 address in AWS that persists independently of the EC2 instance lifecycle |
Next Steps
- 🔗 Official Certbot Instructions for Nginx + Snap
- 🔗 Let's Encrypt Rate Limits Documentation
- 🔗 AWS Elastic IP Addresses — Official Docs
- Consider migrating to AWS Certificate Manager (ACM) with an Application Load Balancer if you need managed certificate rotation at scale — ACM certificates are free for use with AWS load balancers and auto-renew without any Certbot setup.
Comments
Post a Comment