Renewing SSL Certificates on HAProxy Running on an Azure VM (Step-by-Step)

If you’re running HAProxy on an Azure VM, sooner or later you’ll need to renew your SSL/TLS certificate. When that renewal is handled cleanly, users see nothing. When it’s handled poorly, you get outages, browser warnings, or failed health checks.

This guide walks you through a safe renewal process—covering both Let’s Encrypt (Certbot) and commercial/internal CA certificates—plus how to build the PEM file HAProxy expects, reload HAProxy without downtime, and verify the new cert is live.


Why this matters

SSL certificates expire. When they do:

  • Browsers show scary warnings (loss of trust)
  • APIs fail due to TLS errors
  • Monitoring and integrations break
  • Your team gets paged at the worst time

A repeatable renewal process avoids all of that.


Before you start: Know what HAProxy is using

HAProxy usually terminates TLS at port 443, and it expects certificates in a specific format—most commonly a single .pem file that contains:

  1. Server certificate
  2. Intermediate chain
  3. Private key

First, find where your HAProxy config points to the cert.

Find certificate references in HAProxy configuration

sudo grep -RniE "bind .*ssl|crt |crt-list" /etc/haproxy/

You will typically see one of these patterns:

  • Direct PEM reference bind *:443 ssl crt /etc/haproxy/certs/site.pem
  • Certificate list reference crt-list /etc/haproxy/crt-list.txt

If it’s a crt-list, check that file too:

sudo cat /etc/haproxy/crt-list.txt

Step 1 — Back up your HAProxy and certificates

Always back up before making changes:

sudo mkdir -p /root/haproxy-cert-backup/$(date +%F)
sudo cp -a /etc/haproxy /root/haproxy-cert-backup/$(date +%F)/

If something goes wrong, you can instantly roll back.


Step 2 — Confirm the current certificate expiry date

Check the live certificate presented to users

Replace yourdomain.com with your real domain:

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

Look at the notAfter date—that’s your expiry.


Step 3 — Renew using Let’s Encrypt (Certbot method)

If your cert is managed by Let’s Encrypt, renewal is typically straightforward.

Confirm what Certbot is managing

sudo certbot certificates

This shows the domains and certificate paths (usually in /etc/letsencrypt/live/).

Renew certificates

sudo certbot renew

If you want verbose detail:

sudo certbot renew -v

Where the renewed files usually live

  • /etc/letsencrypt/live/yourdomain.com/fullchain.pem
  • /etc/letsencrypt/live/yourdomain.com/privkey.pem

Step 4 — Renew a commercial or internal CA certificate

If your certificate comes from a commercial CA (DigiCert/Entrust/etc.) or internal PKI, the renewal process usually looks like:

  1. Generate (or reuse) a private key
  2. Generate CSR
  3. Submit CSR to CA
  4. Download issued certificate + chain
  5. Build HAProxy PEM and reload

Generate a CSR (using an existing key)

sudo openssl req -new -key /path/to/yourdomain.key -out /root/yourdomain.csr

Then upload yourdomain.csr to your CA portal, and download:

  • server cert (yourdomain.crt)
  • intermediate chain (chain.crt or multiple files)

Step 5 — Build the HAProxy PEM file (critical step)

HAProxy typically wants a single PEM file combining everything.

Option A: Build PEM from Let’s Encrypt files

DOMAIN="yourdomain.com"
sudo install -d -m 700 /etc/haproxy/certs

sudo bash -c "cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/certs/$DOMAIN.pem"
sudo chmod 600 /etc/haproxy/certs/$DOMAIN.pem
sudo chown root:root /etc/haproxy/certs/$DOMAIN.pem

Option B: Build PEM from CA-issued files

sudo install -d -m 700 /etc/haproxy/certs

sudo bash -c "cat /root/newcert/yourdomain.crt /root/newcert/chain.crt /path/to/yourdomain.key > /etc/haproxy/certs/yourdomain.pem"
sudo chmod 600 /etc/haproxy/certs/yourdomain.pem
sudo chown root:root /etc/haproxy/certs/yourdomain.pem

Step 6 — Verify the certificate matches the private key

This avoids the classic “wrong key” problem that causes HAProxy to fail to start.

sudo openssl x509 -noout -modulus -in /etc/haproxy/certs/yourdomain.pem | openssl md5
sudo openssl rsa  -noout -modulus -in /etc/haproxy/certs/yourdomain.pem | openssl md5

✅ The MD5 outputs must be identical.


Step 7 — Validate HAProxy configuration before reloading

Always test the config first:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg

If you see Configuration file is valid you’re safe to proceed.


Step 8 — Reload HAProxy with minimal disruption

Preferred: Reload (no full restart)

sudo systemctl reload haproxy

If reload isn’t supported in your build (rare), restart:

sudo systemctl restart haproxy

Then confirm HAProxy is healthy:

sudo systemctl status haproxy --no-pager
sudo journalctl -u haproxy -n 50 --no-pager

Step 9 — Confirm the new certificate is live

Run the live check again:

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

Verify that notAfter is now the renewed expiry date.


Step 10 — Automate renewals (recommended for Let’s Encrypt)

To make renewals truly “set and forget,” add a Certbot deploy hook to reload HAProxy whenever certs renew.

sudo tee /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh >/dev/null <<'EOF'
#!/bin/bash
set -e
systemctl reload haproxy
EOF

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy-reload.sh

Test safely with a dry run:

sudo certbot renew --dry-run

Azure-specific gotchas (quick checklist)

  • NSG inbound rules: If using HTTP-01 challenge, port 80 must be reachable.
  • VM firewall: UFW/firewalld can block 80/443 even if NSG allows it.
  • Locked-down environments: If port 80 isn’t allowed, DNS-01 challenge is usually required.
  • Missing chain: Always use fullchain.pem (not only cert.pem) to avoid browser chain errors.

Wrap-up

Renewing SSL certs on HAProxy doesn’t need to be stressful. The key is:

  • know where HAProxy reads the certificate
  • renew correctly (Let’s Encrypt or CA)
  • build the correct HAProxy PEM format
  • validate, reload, and verify

Once you have this process documented, you can renew in minutes—and automate the rest.