On a VPS there is no AutoSSL doing this for you. Certificates are issued by a client you install and run, and the whole arrangement lives or dies on whether renewal is automatic, because certificates last about 90 days and a forgotten one takes the site down.
Install the client
Certbot is the usual choice and is packaged for every mainstream distribution. Install it from the distribution repositories rather than from a script, so security updates arrive through the same mechanism as everything else.
Install the plugin matching your web server. There are separate ones for Apache and Nginx, and they configure the server for you instead of leaving you to paste paths.
Issue a certificate
sudo certbot --nginx -d example.com -d www.example.com
Include every name the site answers on. A certificate covering the bare domain but not www shows a warning to everyone who types the www form, and that is easy to miss when you always use one of them yourself.
The client asks for an email address. Use one you read, expiry warnings go there, and they are the last line of defence if renewal breaks.
The domain must already resolve here
Verification works by the certificate authority making a request to your domain. If DNS still points somewhere else, it fails, and it fails the same way however many times you retry.
So the order is fixed: DNS first, certificate second. This is the single most common reason issuance fails, and no amount of reconfiguring the client will change it.
Port 80 has to be open
The standard verification uses HTTP, so the firewall must allow port 80 even on a site that redirects everything to HTTPS.
Blocking 80 entirely is a reasonable instinct and it breaks renewal silently. The certificate works until it expires, and then the site stops.
Wildcards need DNS verification
A wildcard certificate covering all subdomains cannot be verified over HTTP. It requires proving control of the DNS zone by creating a record, which means either doing it by hand at each renewal or using a plugin for your DNS provider's API.
By hand is not viable on a 90-day cycle. If you want a wildcard, set up the API integration or accept issuing per-subdomain certificates instead.
Renewal is the part that matters
Certbot installs a timer or cron entry that attempts renewal twice a day and does nothing unless a certificate is within 30 days of expiry. That margin is deliberate: it gives you a month of failed attempts before anything breaks.
Confirm it exists and works:
sudo certbot renew --dry-run
Run that once after setup. A dry run that succeeds means real renewal will too, and it is the difference between assuming and knowing.
Why renewal fails, when it fails
Three causes cover nearly all of it.
Port 80 was closed after the initial issuance, often while tightening the firewall.
DNS changed and the domain no longer resolves to this server.
The web server configuration changed in a way that breaks the verification path: an aggressive redirect rule catching the challenge URL is the usual version.
All three are silent. The site works until the certificate expires, and then every visitor sees a full browser warning.
Get told before it expires
The email address you gave the client receives warnings from the certificate authority. Make sure it is one you read, and not an address on the domain the certificate protects.
Better, add an external check that watches the certificate's expiry date and alerts you. Monitoring that runs on the server cannot tell you the server's certificate has expired if the server is also the thing being monitored.
After issuance
Redirect HTTP to HTTPS at the web server, so old links and indexed URLs move across rather than continuing to serve the insecure version.
Then check for mixed content, because a valid certificate on a page pulling images over HTTP still shows a broken padlock. Fixing mixed content warnings goes into it.
And verify from outside rather than from the machine you configured. Missing intermediate certificates are invisible on the server itself. The SSL troubleshooting guide walks through checking with openssl.
Confirm the renewal works before you depend on it
Automatic renewal is arranged once and verified almost never, and the first real attempt happens weeks later when nobody is watching.
certbot renew --dry-run 2>&1 | tail -5 systemctl list-timers --all --no-pager | grep -i certbot crontab -l | grep -i certbot
The dry run performs everything except the issuance, so a validation problem appears now rather than at the expiry.
Confirm something is actually scheduled as well. A client installed by hand frequently has no timer at all, and the certificate works perfectly for its full term before failing once.
Reload the services that hold the certificate
Renewal writes new files. Every service that presented the old one keeps presenting it until it is told otherwise.
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate echo | openssl s_client -connect example.com:993 2>/dev/null | openssl x509 -noout -enddate
Different dates on different ports is the signature. The renewal succeeded and one service is still serving the previous file.
Attach a reload to the renewal itself rather than doing it by hand, and check each port afterwards rather than only the website. Mail is the one that gets missed, and mail clients warn every customer rather than only visitors.
Watch the expiry independently
The renewal mechanism reporting success is not the same as a valid certificate being served.
for d in example.com www.example.com mail.example.com; do printf '%-26s %s\n' "$d" "$(echo | openssl s_client -connect "$d":443 -servername "$d" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null || echo 'yanit yok')" done
Check what is actually presented, from outside, on a schedule. That single check catches every failure mode at once: a renewal that stopped, a service that was not reloaded, and a name that fell out of coverage.
Anything with less than two weeks remaining and no sign of renewal deserves attention that day, since the retries have already been failing for a while by then.