Next goal: stop serving this off a bare IP and get it behind a real domain with real HTTPS.
Free domain
Used DuckDNS for a free subdomain. First attempt at pointing it went into the wrong field entirely — DuckDNS splits IPv4 and IPv6 into separate boxes, and the server's IP ended up typed into the IPv6 one by mistake. Fixed by moving it to the right field.
Elastic IP first
Before pointing a domain at anything, allocated an Elastic IP and associated it with the instance, so the address wouldn't shift on a stop/start. Associating it changed the public IP immediately — and broke the site's styling, because Bludit had the old IP baked into its site config as the base URL. Every CSS and JS request was still trying to load from an address that no longer pointed here.
sudo sed -i 's/OLD_IP/NEW_IP/g' bl-content/databases/site.php
Certbot
Amazon Linux doesn't ship certbot in its repos, so it went in through a Python venv instead:
sudo dnf install -y python3 augeas-libs
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-nginx
sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot
First run failed — certbot couldn't find a matching nginx server block, because nginx.conf still had server_name _;, a wildcard that doesn't match a real domain name. Swapped it for the actual domain, reloaded nginx, then re-ran the install step and the cert deployed cleanly.
The mixed-content trap, again
Switching to https:// broke styling a second time, same root cause as the IP change: the site's base URL was still saved as http://, so the browser blocked every asset as insecure mixed content. One more sed against site.php, this time swapping the scheme, fixed it for good.
What I actually learned
Whatever stores your site's base URL will bite you twice — once when the IP changes, once when the scheme does. Elastic IP before DNS, DNS before certs, in that order, saves a redo. And server_name _; is a real value, not a placeholder — certbot takes it literally.