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:
- Server certificate
- Intermediate chain
- 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:
- Generate (or reuse) a private key
- Generate CSR
- Submit CSR to CA
- Download issued certificate + chain
- 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.crtor 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 onlycert.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.

