A free certificate from Let's Encrypt takes about five minutes to set up and renews itself after that. This guide covers a plain website and a reverse proxy in front of an app.
1. Point your domain at the server first
Certificates are issued only after Let's Encrypt confirms that the domain points at your server. Create both records at your DNS provider before you start:
example.com A 203.0.113.10 example.com AAAA 2001:db8:1:2::1
Use your server's own addresses, which are shown on your service page. Check that they resolve before continuing:
$ dig example.com A +short $ dig example.com AAAA +short
2. Install Nginx and open the web ports
$ sudo apt update && sudo apt install -y nginx $ sudo ufw allow 80,443/tcp
$ sudo dnf install -y nginx $ sudo systemctl enable --now nginx $ sudo firewall-cmd --permanent --add-service=http --add-service=https $ sudo firewall-cmd --reload
Both firewall tools apply these rules to IPv4 and IPv6 at the same time, so there is nothing extra to do for IPv6.
3. Create a server block
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
}The [::] line is the IPv6 listener. Without it your site answers on IPv4 only, and visitors on IPv6 networks get nothing.
$ sudo mkdir -p /var/www/example.com $ echo "it works" | sudo tee /var/www/example.com/index.html $ sudo nginx -t && sudo systemctl reload nginx
Always run nginx -t before a reload. It checks the configuration and tells you the exact line if something is wrong.
4. Install Certbot and get the certificate
The Certbot project recommends installing from snap, because that version gets updates fastest.
$ sudo apt install -y snapd $ sudo snap install --classic certbot $ sudo ln -s /snap/bin/certbot /usr/bin/certbot
$ sudo dnf install -y epel-release $ sudo dnf install -y certbot python3-certbot-nginx
Then ask for the certificate. Certbot edits your Nginx configuration and reloads it:
$ sudo certbot --nginx -d example.com -d www.example.comAnswer the email and terms questions. When it finishes, your site answers on HTTPS, and Nginx redirects HTTP to HTTPS.
Let's Encrypt has rate limits
Repeated failed attempts for the same domain will lock you out for a while. If you are testing, add --dry-run to your command first. It runs the whole process against a test server without issuing a real certificate.
5. Check that renewal works
Certificates last 90 days. The installation adds a timer that renews them automatically, so the only thing to verify is that the renewal itself succeeds:
$ sudo certbot renew --dry-run $ systemctl list-timers | grep -i certbot
If the dry run passes, renewal will work unattended.
6. Reverse proxy for an app on a local port
If you run an app in Docker or on a local port, Nginx sits in front of it and handles HTTPS. Replace the root and index lines with a proxy block:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}The last three lines matter for any app with a live dashboard or chat, because those use WebSockets and will not load without them. Then run sudo nginx -t, reload, and run the same certbot --nginx command with -d app.example.com.
With the proxy working, close the app's own port in your firewall so the only way in is through Nginx over HTTPS.
Quick reference
| Task | Command |
|---|---|
| Test config | sudo nginx -t |
| Reload | sudo systemctl reload nginx |
| Get a certificate | sudo certbot --nginx -d example.com |
| Test issuance safely | add --dry-run |
| Test renewal | sudo certbot renew --dry-run |
| List certificates | sudo certbot certificates |
Where to go next
Add security headers and a stronger TLS configuration once the basics work. If your site is one of several on the same server, repeat step 3 with a new file per domain; Nginx picks the right one by server_name.