Target outcome: The website on a Vultr VPS running Ubuntu and Nginx has a Let’s Encrypt certificate for the correct domain, is accessible over HTTPS, and has had its renewal process tested with sudo certbot renew --dry-run. This article is intended for users with SSH access and sudo. Commands prefixed with sudo are run in an SSH session on the VPS; commands prefixed with dig can be run on a personal computer or the VPS.
This procedure uses HTTP-01 validation. The domain must therefore point to the VPS, and the server must accept Internet connections over TCP port 80. HTTPS additionally requires TCP port 443. Let’s Encrypt certificates are valid for 90 days; successfully installing a certificate does not mean that the renewal process is working.
Scope, prerequisites, and values to replace
- A Vultr VPS running Ubuntu 22.04 or Ubuntu 24.04.
- Nginx is already installed or will be installed in this article.
- An SSH account with
sudo. - You have permission to manage the domain’s DNS and the Vultr Firewall Group if the VPS uses that firewall group.
- In the commands below, replace
example.comwith the actual domain name. If you do not usewww, omit the-d www.example.comargument and the corresponding DNS records.
TLS certificates do not depend on PHP. If the website is a PHP application or WordPress site, PHP-FPM and the PHP extensions must work independently; you do not need to change upload_max_filesize, memory_limit , or max_execution_time solely to issue the certificate. You can check PHP after HTTPS is working.
1. Point DNS and check Nginx before issuing the certificate
Configure DNS records
At the domain registrar or DNS provider managing the domain, create at least the following records:
| Type | Name | Value | Purpose |
|---|---|---|---|
| A | @ | The Vultr VPS’s IPv4 address | Root domain |
| A | www | The Vultr VPS’s IPv4 address | Domain name www |
If an AAAArecord exists, keep it only if the VPS actually has IPv6 and Nginx/the firewall serve IPv6 traffic. An incorrect AAAA record can cause the validation server to reach the wrong destination even when the A record is correct.
Run this from a personal computer or from the VPS:
dig +short A example.com
dig +short A www.example.com
dig +short AAAA example.com
dig +short AAAA www.example.com
The record result A must be the VPS’s IPv4 address. If there is a AAAA, that address must be the VPS’s actual IPv6 address. Newly changed DNS records may not have propagated everywhere because of TTLs and DNS caches; do not run Certbot until the resolution results are correct.
Check Nginx and the server block
On the VPS, run:
sudo systemctl status nginx --no-pager
sudo nginx -t
curl -I http://example.com
The expected result is that Nginx is running, nginx -t reports a valid configuration, and curl receives an HTTP response. The server block must contain the correct domain name, for example:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
}
The example above is only for a static website. If you are using PHP-FPM, a reverse proxy or WordPress, keep the location directives that serve the application; do not replace the entire server block with this example.
For a PHP website, you can also check on the VPS:
php -v
php -m
systemctl list-units --type=service --all 'php*-fpm.service' --no-pager
If the application uses PHP-FPM, the service name usually has the form php8.1-fpm or php8.3-fpm, depending on the installed version. A PHP-FPM error is not a certificate-issuance error, but it can cause the website to return 502 after HTTPS is enabled.
2. Open TCP ports 80 and 443 in both firewall layers
HTTP-01 requires TCP port 80; an HTTPS website requires TCP port 443. Check both the Vultr Firewall Group and the firewall inside Ubuntu. Vultr’s related documentation is available at How Do I Install an SSL Certificate on a Vultr Compute Instance?.
In the Vultr control panel, open the Firewall Group attached to the VPS and allow:
- TCP port 22 from your administrative IP address, or the actual SSH port if you changed it.
- TCP port 80 from the Internet.
- TCP port 443 from the Internet.
On the VPS, replace 22 with the actual SSH port if you do not use the default port. Run each command:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
If UFW has never been enabled, the sudo ufw enable command may ask you to confirm. Continue only after allowing the correct SSH port; otherwise, you could lock yourself out of the administrative session. The verification output must show rules for SSH, HTTP and HTTPS.
To test from a network outside the VPS, use your personal computer or another server:
curl -I http://example.com
If the command times out, check DNS, the Vultr Firewall, UFW and any other firewall before running Certbot.
3. Back up the configuration and install Certbot
Back up the configuration before Certbot modifies Nginx
On the VPS, create a timestamped backup:
BACKUP_DIR="/root/nginx-backup-$(date +%F-%H%M%S)"
sudo cp -a /etc/nginx "$BACKUP_DIR"
printf '%sn' "$BACKUP_DIR"
The final command prints the backup path. Record this path; you will need it if you have to roll back. Verify that the directory exists:
sudo test -d "$BACKUP_DIR" && echo "Da tao backup: $BACKUP_DIR"
Install Nginx if the VPS does not have it
If Nginx is already running and sudo nginx -t succeeds, skip the Nginx installation step. On a new VPS, run the following on the VPS:
sudo apt update
sudo apt install -y nginx snapd
sudo systemctl enable --now nginx
sudo nginx -t
The final output must report a valid configuration. If Nginx does not yet have a server block for the domain, return to the DNS step and configure the server block before requesting a certificate.
Install Certbot through Snap
On the VPS, run these commands in order:
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -sfn /snap/bin/certbot /usr/local/bin/certbot
certbot --version
The final command must print the Certbot version. Avoid installing multiple Certbot versions from Snap, APT and pip at the same time unless necessary, because you may check the wrong program or the wrong renewal task.
4. Issue the certificate and enable HTTPS for Nginx
On the VPS, run the following command and replace the example domain with the actual domain:
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask for an email address, acceptance of the terms of service and whether to redirect HTTP to HTTPS. For a public website, you can choose the redirect if the application does not depend on plain HTTP. Do not request a certificate for a domain that does not resolve to the VPS.
The Nginx plugin will find the appropriate server block, add the TLS configuration and may reload Nginx. If the command fails, do not delete existing certificates or configuration; record the error message and then check DNS, port 80 and the server block.
Certbot uses its own management paths under /etc/letsencrypt. Do not delete files there manually. After the command succeeds, check:
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certificates
certbot certificates must show the correct domain name, expiration date and certificate path. If nginx -t fails, do not reload Nginx until the syntax error has been fixed.
5. Check HTTPS, HTTP redirection and the application
On the VPS or a personal computer, run:
curl -I http://example.com
curl -I https://example.com
curl -sS -o /dev/null -w '%{http_code}n' https://example.com
Expected results:
- HTTP returns
301or308redirects to HTTPS if you selected the redirect option. - HTTPS returns the website’s response without a certificate error.
- The certificate covers every domain name you access, such as both
example.comandwww.example.com.
The code 200, 301 or a code returned by the application itself may also be valid, depending on the configuration. If HTTPS returns 502, check PHP-FPM or the upstream:
sudo systemctl status nginx --no-pager
sudo journalctl -u nginx -n 50 --no-pager
systemctl list-units --type=service --all 'php*-fpm.service' --no-pager
If the website uses PHP, check the correct PHP-FPM version and the extensions required by the application. Do not treat changing php.ini as a mandatory SSL step; adjust upload, memory, and execution-time limits only when the application has a specific requirement.
6. Check automatic renewal
Check the timer or cron job
Certbot usually installs a systemd timer or a cron job that periodically checks whether certificates are nearing expiration. On the VPS, find the timer:
systemctl list-timers --all | grep -i certbot || true
systemctl list-unit-files | grep -i certbot || true
systemctl status snap.certbot.renew.timer --no-pager
The timer name may vary by version and installation method. If snap.certbot.renew.timer does not exist, use the results of the first two commands to identify the correct unit; do not create an additional cron job when a timer is already present.
If you cannot find a timer, check cron:
sudo grep -R "certbot renew" /etc/cron.d /etc/cron.daily /var/spool/cron 2>/dev/null || true
Distinguish between two tasks: the timer or cron job only starts the command; DNS, the firewall, Nginx, and the domain configuration must still be correct for renewal to succeed.
Simulate a renewal
This is the most important check after issuing the certificate. On the VPS, run:
sudo certbot renew --dry-run
--dry-run uses the staging environment and does not replace the certificate currently in use. A successful result should show that the simulated renewal completed without authentication or deployment errors. If it fails, read the complete output to identify a DNS, HTTP-01, firewall, Nginx, or deployment-hook error.
After the simulation succeeds, check Nginx and the website again:
sudo nginx -t
sudo systemctl reload nginx
curl -sS -o /dev/null -w '%{http_code}n' https://example.com
manually every month. Use that command only when there is a clear operational reason; the timer checks automatically on schedule. certbot renew There is no need to run
7. Diagnose problems by symptom
| Symptom | Common cause | Check and fix |
|---|---|---|
| Validation timeout | TCP port 80 is blocked by the Vultr Firewall, UFW, or another firewall. | Check sudo ufw status verbose, the Firewall Group, and run curl -I http://example.com from an external network. |
| Incorrect validation IP | The A or AAAA record points to another server. | Run dig +short A example.com and dig +short AAAA example.com; correct the DNS record or remove the incorrect AAAA record. |
| 404 at the ACME path | A rewrite rule, reverse proxy, or application is handling /.well-known/acme-challenge/incorrectly. | Check the server block and rewrite rules; ensure that requests for the domain on port 80 reach the correct Nginx server. |
| Nginx will not reload | The TLS configuration contains a syntax error or conflicts with an older configuration. | Run sudo nginx -t, fix the reported file, and then run sudo systemctl reload nginx. |
--dry-run failed | DNS, the firewall, the domain, or the web configuration has changed. | Read the Certbot output and logs, fix the cause, and try again; do not delete the existing certificate while investigating. |
| Certificate does not match the domain | The certificate does not include the variant being accessed, such as www. | Run sudo certbot certificates; if necessary, issue a new certificate with the complete set of -dparameters. |
| HTTPS returns 502 | PHP-FPM or the upstream behind Nginx is not running. | Check the PHP-FPM/upstream service status and the Nginx logs; this is not necessarily a certificate problem. |
Let’s Encrypt uses HTTP-01 by placing a token at /.well-known/acme-challenge/ and then retrieving that token from the Internet. Therefore, opening only TCP port 443 is not sufficient for this method. See also Challenge Types — Let’s Encrypt.
8. Safely roll back a broken Nginx configuration
If Nginx fails its configuration test or the website breaks after Certbot makes changes, do not run another issuance or renewal command first. Identify the error:
sudo nginx -t
sudo certbot certificates
sudo ls -la /etc/letsencrypt/live/example.com/
Replace example.com with the actual domain name. If you need to restore the backup created in step 3, specify the correct path before running:
BACKUP_DIR="/root/nginx-backup-YYYY-MM-DD-HHMMSS"
if sudo test -d "$BACKUP_DIR"; then
sudo mv /etc/nginx "/etc/nginx.failed-$(date +%F-%H%M%S)"
sudo cp -a "$BACKUP_DIR" /etc/nginx
sudo nginx -t
sudo systemctl reload nginx
else
echo "Khong tim thay thu muc sao luu: $BACKUP_DIR" >&2
exit 1
fi
Replace YYYY-MM-DD-HHMMSS with the actual backup directory name. The command checks that the directory exists before restoring, helping prevent the wrong path from being used. Do not delete /etc/letsencrypt while investigating; it contains the certificates, keys, and data Certbot needs. Redeploy the certificate only after you have fixed the cause and sudo nginx -t successfully completed the process.
Acceptance checklist
- The A records for the domains you need to use point to the VPS's correct IPv4 address.
- AAAA records either do not exist or point to the IPv6 address actually served by the VPS.
- The Vultr Firewall and UFW allow TCP 80 and TCP 443; SSH access still works.
sudo nginx -tsuccessfully before and after running Certbot.sudo certbot certificatesshows the correct domain names and expiration date.- HTTPS loads successfully, and the certificate covers every required domain.
- HTTP redirects correctly if you selected the redirect option.
- Certbot's systemd timer or cron job exists and has not been disabled.
sudo certbot renew --dry-runcompletes without errors.- You have the path to the Nginx configuration backup and know how to restore it.
If you manage multiple websites or VPSs, record the domain names, TLS termination point, authentication method, timer/cron mechanism, and the date on which it last ran --dry-run. You can also read the article on enabling HTTPS and fixing CAA errors for Blogger, the blog security category, and the Hosting and Servers guide for other HTTPS configuration examples when needed.
Conclusion
For a reliable Let's Encrypt installation on a Vultr VPS, complete the entire sequence: correct DNS, TCP ports 80 and 443 open in both firewall layers, an Nginx server block with the correct domain name, a certificate successfully issued by Certbot, and sudo certbot renew --dry-run a successful renewal test. After every DNS, firewall, or Nginx change, rerun the test procedure rather than relying only on the lock icon in the browser.
Reference source
- How Do I Install an SSL Certificate on a Vultr Compute Instance? — Vultr Docs.
- Obtain TLS certificates — Ubuntu Server documentation.
- Certbot Instructions — Certbot.
- Challenge Types — Let’s Encrypt.
- Firewall — Ubuntu Server documentation.

