VPS.TC
| $
Server Status
Turkey Istanbul, Türkiye
Active
USA New York, USA
Active
Cart Total:
View Cart
How to Install an SSL Certificate on a VPS with Let’s Encrypt
How To?

How to Install an SSL Certificate on a VPS with Let’s Encrypt

Avatar of Defne Defne August 29, 2026 12 min read 0 Comments
Share:

What to check before installing an SSL certificate on a VPS

That padlock in a browser does not appear just because a certificate file exists somewhere under /etc. For me, it means the entire path is working: DNS points to the right machine, the web server answers on the expected ports, HTTP validation can reach the VPS, and renewal will happen before the certificate expires.

Let’s Encrypt makes this free and automatable. On most Linux VPSs, I use Certbot with Nginx. Apache follows much the same route, but it needs a different plugin. Before running anything, find out which web server is active. The right command on the wrong machine is still the wrong command.

Prepare the domain and the VPS

Check DNS before starting. example.com and, if you use it, www.example.com should point to the VPS’s public IPv4 address. If you use IPv6, check the AAAA record too. An old or incorrect AAAA record can send validation requests to another server while IPv4 appears perfectly healthy.

🚀 Boost Your Speed with VPS Server!

Speed up your projects with high-performance SSD storage and 99.9% uptime guarantee.

Get Started

I normally start in the terminal rather than in a hosting panel:

dig +short example.com A
dig +short example.com AAAA
dig +short www.example.com A

The first command should show the VPS’s IPv4 address. The second should show the IPv6 address, if one is configured. An empty result can mean the record is missing or that the DNS change has not reached the resolver you are using yet. Depending on the TTL, you may need to wait. Running Certbot repeatedly will not make DNS update faster.

Next, see what is listening:

☁️ Gain Flexibility with Cloud Server!

Experience the power of cloud with scalable resources and instant backups.

Explore
sudo ss -tulpn | grep -E ':(80|443)b'
sudo ufw status verbose

Let’s Encrypt needs to reach TCP port 80 from the internet when you use HTTP-01 validation. Port 443 must also be reachable for normal HTTPS traffic. With UFW, this profile opens both ports:

sudo ufw allow 'Nginx Full'
sudo ufw status

This changes the firewall on the VPS. If your provider has a security group, or you also use nftables or an external firewall, allow the same traffic there. Opening a port in UFW does not open a port blocked by the provider’s network rules.

Install Certbot

On Debian and Ubuntu, Certbot and its web-server plugins are available through the distribution package manager on many supported releases. Some Ubuntu installations use Snap instead. I generally prefer apt on Debian systems so package management stays consistent with the rest of the server, but check the instructions for the operating system you actually run.

First identify the operating system and the web server:

cat /etc/os-release
nginx -v
apache2 -v

If one of the web-server commands is not installed, it will print an error. That is fine; it is still useful information.

For Nginx on Debian or Ubuntu:

sudo apt update
sudo apt install certbot python3-certbot-nginx

For Apache, install its plugin instead:

sudo apt install certbot python3-certbot-apache

The plugins can read your virtual host configuration and make certificate installation easier. I still back up the configuration before allowing an automated tool to edit a production server. A small mistake can affect an unexpected site, especially on a VPS hosting several domains.

sudo cp -a /etc/nginx /etc/nginx.backup.$(date +%F)
sudo nginx -t

You should see syntax is ok and test is successful in the nginx -t output. If you do not, stop there and fix the configuration first.

Use Let’s Encrypt with Nginx

Make sure the domain is present in an Nginx server block. This is a small HTTP-only example:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.html index.php;

    location / {
        try_files $uri $uri/ =404;
    }
}

Save it as, for example, /etc/nginx/sites-available/example.com, then enable it:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl reload nginx

reload makes Nginx reread its configuration without dropping existing connections. Before requesting the certificate, open the domain over HTTP and confirm that the expected site appears. If you see the default Nginx page, the hostname match is not correct yet.

Checking the hostname before changing Nginx in production is not theoretical advice for me. I once changed a server I thought was staging and found out, rather late, that it was production. That was the day I started using a red shell prompt on production systems. I still run hostname before editing a configuration file.

Now run Certbot with the Nginx plugin:

sudo certbot --nginx -d example.com -d www.example.com

Certbot asks for an email address, acceptance of the terms, and whether HTTP traffic should be redirected to HTTPS. For a normal public site, I choose the redirect. For an application behind a proxy, I first check how X-Forwarded-Proto is handled so I do not create a redirect loop.

After a successful run, inspect the certificate:

sudo ls -l /etc/letsencrypt/live/example.com/
sudo certbot certificates

The files under live are symbolic links to the current certificate version, not separate permanent copies. Pointing Nginx at these paths means you do not need to edit the configuration after every renewal:

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Certbot normally adds these directives itself. If you configure them manually, use fullchain.pem for the certificate. Using only cert.pem can cause intermediate-chain problems with some clients.

Apache takes a different plugin

On an Apache VPS, the process is similar. Confirm that the domain is defined in the virtual host, then run:

sudo certbot --apache -d example.com -d www.example.com

Certbot can add the SSL configuration to the Apache virtual host and redirect HTTP traffic to HTTPS. Test the configuration after the change:

sudo apachectl configtest
sudo systemctl reload apache2

Syntax OK means the configuration is syntactically valid. If your application runs on PHP-FPM, Node.js, or Docker behind Apache, the certificate usually belongs on the public-facing Apache or Nginx layer, not inside the application itself.

For a Docker VPS, decide where the reverse proxy will live before adding containers. Instead of distributing separate certificates to every container, let Nginx Proxy Manager, Traefik, or Nginx on the host terminate TLS for incoming traffic. The port-checking approach in How to Install Docker on a VPS and Run Your First Container is useful here, particularly before the first container starts competing for ports.

Check the HTTPS redirect

After installation, check the certificate endpoint and the redirect from the terminal:

curl -I http://example.com
curl -I https://example.com

The first command will usually return 301 or 308 with a header such as Location: https://example.com/. A 200 from the second request is a good sign, although an application may return a different status on its login page. The useful checks are that the TLS handshake succeeds and that the intended certificate is being served.

OpenSSL can show the certificate details:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

The -servername option matters. When several domains share one IP address, testing without SNI may show the default virtual host’s certificate instead. Check notBefore, notAfter, subject, and issuer in the output.

Renewal automation is the part that matters

Let’s Encrypt certificates are short-lived, so installing one on a VPS is only half the job. Test renewal before the expiry date becomes somebody’s emergency:

systemctl list-timers | grep certbot
systemctl status certbot.timer
sudo certbot renew --dry-run

--dry-run tests the renewal path without replacing the live certificate. A successful run is a good indication that DNS validation, port access, and the web-server reload all work. If it fails, fix it now. The day a certificate expires is a poor time to discover that a timer has been disabled.

After renewal, confirm that Nginx or Apache loads the new certificate. The files can change while the running process continues serving the old certificate from memory. Certbot normally handles the reload, but a custom setup may need a deploy hook:

sudo install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
sudo vim /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx

Make the hook executable:

sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

The hook runs during Certbot’s deploy phase, after a certificate has actually been renewed. I would rather connect the reload to that renewal flow than add a cron line that reloads Nginx unnecessarily every day.

Do not assume renewal works because a timer exists. I monitor the result of certbot renew --dry-run and check the served certificate separately. An availability alarm that only checks the HTTP status can report the site as healthy while the certificate is already expired.

HTTP-01 and DNS-01 validation

Certbot uses challenge methods to prove that you control the domain. For a standard Nginx setup, HTTP-01 is usually the simplest. Let’s Encrypt tries to fetch a temporary file under http://example.com/.well-known/acme-challenge/.... Completely blocking port 80, or rejecting every HTTP request in the application layer, can break validation.

HTTP-01 does not support wildcard certificates. For *.example.com, use DNS-01 instead. Certbot plugins for DNS providers can create the required TXT record through an API. If you use the Cloudflare DNS plugin and store its API credentials in a file, restrict the permissions:

sudo chmod 0600 /root/.secrets/certbot/cloudflare.ini

Do not put the token in shell history or in a playbook that everyone can read. The DNS API token should have only the permission to create and delete TXT records in the relevant zone. Small permissions matter when a token leaks.

Common failures and how I diagnose them

Timeout or connection refused

This usually points to network access, not to the certificate. First confirm that DNS returns the right IP, then check port 80 in both the VPS firewall and the provider security group. Nginx listening only on 127.0.0.1:80 will also prevent external validation.

sudo ss -ltnp | grep ':80'
curl -4 -I http://example.com
curl -6 -I http://example.com

If IPv4 works but IPv6 does not, and the domain has a bad AAAA record, correct DNS. I hit this during a VPS migration: I updated the A record and missed the old AAAA record. Clients using IPv6 continued visiting the previous server.

Too many redirects

If you use a proxy such as Cloudflare, its SSL mode must agree with the origin server’s HTTPS configuration. A proxy can accept HTTPS externally and connect to the VPS over HTTP, while the VPS redirects that request back to HTTPS. That combination creates a loop. Make sure the application interprets proxy headers correctly.

Rate-limit errors

Do not repeat failed requests over and over. Let’s Encrypt applies validation and certificate limits in production. Test the flow against its staging environment first:

sudo certbot certonly --staging --nginx -d example.com

Staging certificates are not trusted by browsers; they are only for testing the installation flow. After testing, check that the staging certificate has not been left in the live configuration.

The web server cannot reload

Certbot may obtain the certificate successfully while the Nginx reload fails because of an unrelated configuration error. Start with:

sudo nginx -t
sudo journalctl -u nginx -n 80 --no-pager

Restarting before reading the logs was one of my early-career mistakes. When certificate installation goes wrong, I test the configuration and inspect the relevant logs before restarting anything. That avoids cutting existing connections for no reason.

Harden TLS carefully

Certbot’s defaults are a sensible starting point for most sites. Before writing a custom cipher list, understand the application’s client requirements. If you do not need support for old devices, TLS 1.2 and TLS 1.3 are a reasonable baseline:

ssl_protocols TLSv1.2 TLSv1.3;

Do not rush into HSTS. The Strict-Transport-Security header tells browsers to use HTTPS for the domain. Do not enable includeSubDomains, and especially not preload, until every subdomain is ready for HTTPS. A forgotten subdomain can become unexpectedly difficult to reach.

Before enabling HSTS, check the site’s assets, API endpoints, and third-party content. Images, JavaScript, or fonts loaded over HTTP will create mixed-content warnings. The browser’s developer console is a quick way to find these references.

A certificate is not server security

An SSL certificate encrypts the connection between the client and the server and helps verify the domain. It does not mean that the operating system is patched, SSH is safely configured, or the application has no vulnerabilities.

Handle firewall rules, updates, separate users, SSH keys, and intrusion monitoring as separate tasks. The baseline checks in 10 Essential Steps to Secure and Harden Your Linux Server are a useful starting point, but adjust them to the risks of your own VPS. Changing the SSH port is not security by itself; without log monitoring, it mostly moves the noise somewhere else.

When measuring performance after enabling HTTPS, separate the TLS handshake, HTTP version, and application response time. Arguing over TTFB one millisecond at a time while leaving caching unconfigured feels like polishing the thermometer with the window open. If you are comparing HTTP/2 and HTTP/3, Demystifying Internet Protocols: From TCP/IP to HTTP/3 gives the network-layer context you need.

My post-installation checklist

  • Do the A and, if applicable, AAAA records point to the correct VPS?
  • Are TCP ports 80 and 443 open in both the operating system firewall and the provider security group?
  • Is Nginx or Apache loading the intended virtual host?
  • Does certbot certificates show the right domain and expiry date?
  • Does certbot renew --dry-run complete successfully?
  • Does the web server reload after renewal and serve the new certificate?
  • Are HTTP requests redirected to HTTPS as expected?
  • Do HSTS and proxy settings avoid redirect loops and mixed content?

Once the installation is complete, add certificate expiry to your monitoring. Uptime Kuma can check site availability, while Prometheus or a dedicated exporter can track certificate expiry. In my Proxmox lab, one of my first tests was exactly this: I tested renewal on a staging domain before moving to a customer domain.

Let’s Encrypt can be installed with a few commands. The real test is the whole path, from DNS to the renewal alert. A certificate working today is not enough. I want to know that it will still work two months from now, during a night shift, without somebody having to intervene manually.

Avatar of Defne
Author

Defne