What this setup does
Nginx listens on ports 80 and 443 and forwards requests to an application that listens only on a private local port, such as 3000 or 8080. The application never faces the internet directly, and Nginx handles HTTPS. This is the usual way to publish a Node.js, Python or Docker application.
You need a domain whose A record already points to the server's public IPv4 address (see the domain and HTTPS guide) and a non-root sudo account. Use either Apache or Nginx on ports 80 and 443, not both. If Apache is already running there, stop and disable it, or use a different server.
sudo ss -lntp | grep -E ":(80|443) " || echo "Ports 80 and 443 are free"Install Nginx and Certbot on Ubuntu
Ubuntu's archive provides Nginx, Certbot and the Certbot Nginx plugin. Let's Encrypt validates your domain by connecting to port 80, so the firewall must allow it.
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
sudo ufw allow "Nginx Full"
sudo systemctl enable --now nginxInstall Nginx and Certbot on AlmaLinux
AlmaLinux provides Nginx in its default repositories and Certbot in EPEL. Open the web ports in firewalld. SELinux blocks Nginx from connecting to other local services by default, so allow that permanently, or the proxy returns 502 errors.
sudo dnf install epel-release
sudo dnf install nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
sudo setsebool -P httpd_can_network_connect 1Start a test application
For a first test, run Python's built-in web server on the loopback address. It stands in for your real application and stops when you press Control-C. Open a second terminal for the following steps, or replace it with your actual app.
mkdir -p ~/proxy-test && cd ~/proxy-test
echo "Hello from the backend" > index.html
python3 -m http.server 8080 --bind 127.0.0.1curl http://127.0.0.1:8080/Create the Nginx site
Create a configuration file for your domain in /etc/nginx/conf.d/. Replace example.com with your domain and 8080 with your application's port. The headers pass the original host name and client address to the application, and the upgrade headers keep WebSocket connections working.
sudo tee /etc/nginx/conf.d/example.com.conf >/dev/null <<'EOF'
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
curl -I http://example.com/Issue the certificate
Certbot proves you control the domain, obtains a certificate, edits your Nginx file to use it and adds a redirect from HTTP to HTTPS. Use a real contact address for expiry notices. Test with the staging flag first if you expect to try several times, because Let's Encrypt limits failed validations.
sudo certbot --nginx --test-cert -d example.com -d www.example.comsudo certbot --nginx -d example.com -d www.example.comConfirm renewal and test the site
Certificates last about 90 days and renew automatically through a systemd timer or cron job installed by the package. A dry run exercises the whole renewal process without changing anything. Then request the site over HTTPS and check the certificate dates.
sudo certbot renew --dry-run
systemctl list-timers | grep -i certbot
curl -I https://example.com/
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -datessudo systemctl enable --now certbot-renew.timer
systemctl list-timers certbot-renew.timerTroubleshoot a 502 or failed validation
A 502 Bad Gateway means Nginx could not reach the application. Check that it is listening on the port in the file and, on AlmaLinux, that the SELinux setting above is on. Failed certificate validation usually means the DNS record is wrong, port 80 is blocked by a firewall, or a stale AAAA record points elsewhere. The error log says which.
sudo ss -lntp | grep 8080
sudo tail -n 30 /var/log/nginx/error.log
sudo tail -n 30 /var/log/letsencrypt/letsencrypt.log
getsebool httpd_can_network_connect 2>/dev/nullOfficial documentation
These upstream references provide more detail on the commands and workflows in this guide.