Let's Encrypt TLS Certificates for Asterisk PJSIP
Need help securing your Asterisk server? I'm available for work
The Problem
Asterisk ships with or is commonly set up with self-signed TLS certificates for SIP-TLS transport. While functional, self-signed certs cause validation warnings in SIP clients and provide no chain of trust. If you already have a Let's Encrypt certificate on the server (e.g., for nginx), Asterisk should use it too.
The Complication
Let's Encrypt stores certificates under /etc/letsencrypt/live/ and
/etc/letsencrypt/archive/, both of which are restricted to root (mode 700).
Asterisk runs as the asterisk user and cannot read these paths directly.
Symlinks will not work because the parent directory permissions block traversal.
Solution: Copy + Deploy Hook
Copy the certificates to Asterisk's key directory with correct ownership, then create a certbot deploy hook to auto-copy on renewal.
Step 1: Replace the Self-Signed Certificate
# Back up the existing self-signed cert
sudo cp /etc/asterisk/keys/asterisk.crt /etc/asterisk/keys/asterisk.crt.self-signed.bak
sudo cp /etc/asterisk/keys/asterisk.key /etc/asterisk/keys/asterisk.key.self-signed.bak
# Copy Let's Encrypt cert (use fullchain for proper chain verification)
sudo cp /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem /etc/asterisk/keys/asterisk.crt
sudo cp /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem /etc/asterisk/keys/asterisk.key
# Set ownership and permissions
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.crt /etc/asterisk/keys/asterisk.key
sudo chmod 600 /etc/asterisk/keys/asterisk.crt /etc/asterisk/keys/asterisk.key
Use fullchain.pem (not cert.pem) for the certificate file. This includes
the intermediate certificate so clients can verify the full chain.
Step 2: Create the Certbot Deploy Hook
sudo tee /etc/letsencrypt/renewal-hooks/deploy/asterisk-cert.sh > /dev/null << 'EOF'
#!/bin/bash
# Deploy Let's Encrypt cert to Asterisk on renewal
# Certbot sets $RENEWED_LINEAGE to the live cert directory
ASTERISK_KEYS=/etc/asterisk/keys
cp "${RENEWED_LINEAGE}/fullchain.pem" "${ASTERISK_KEYS}/asterisk.crt"
cp "${RENEWED_LINEAGE}/privkey.pem" "${ASTERISK_KEYS}/asterisk.key"
chown asterisk:asterisk "${ASTERISK_KEYS}/asterisk.crt" "${ASTERISK_KEYS}/asterisk.key"
chmod 600 "${ASTERISK_KEYS}/asterisk.crt" "${ASTERISK_KEYS}/asterisk.key"
# Restart Asterisk to ensure TLS transport picks up new cert
# (allow_reload=false on TLS transport means a module reload may not suffice)
systemctl restart asterisk 2>/dev/null
logger -t certbot-asterisk "Deployed renewed certificate to Asterisk"
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/asterisk-cert.sh
The deploy hook runs only when a certificate is actually renewed, not on every
certbot run. Certbot sets $RENEWED_LINEAGE to the path containing the new
certs.
We use systemctl restart asterisk rather than asterisk -rx 'module reload
res_pjsip' because the TLS transport has allow_reload = false by default.
A reload may not cause the transport to re-read the certificate files.
Step 3: Reload Asterisk
sudo asterisk -rx 'module reload res_pjsip'
Step 4: Verify
# Check the cert being served on the TLS port
echo | openssl s_client -connect 127.0.0.1:5061 \
-servername YOUR_DOMAIN 2>/dev/null | \
openssl x509 -noout -issuer -subject -dates
# Expected output:
# issuer=C = US, O = Let's Encrypt, CN = R13
# subject=CN = YOUR_DOMAIN
# notBefore=...
# notAfter=...
PJSIP Transport Reference
For reference, the PJSIP TLS transport configuration in pjsip.conf:
[transport-tls]
type = transport
protocol = tls
bind = 0.0.0.0:5061
cert_file = /etc/asterisk/keys/asterisk.crt
priv_key_file = /etc/asterisk/keys/asterisk.key
method = tlsv1_2
verify_client = no
verify_server = no
No changes needed here, the cert files at these paths are now the LE certs.
Certbot Renewal with Firewalled Port 80
If your server's iptables blocks port 80 from the internet (common for PBX servers), certbot's HTTP-01 challenge will fail during renewal. Two approaches:
Option A: Use the nginx Authenticator
If nginx is already running on the server:
# Install the plugin if needed
sudo apt install python3-certbot-nginx
# Update the renewal config
sudo sed -i 's/authenticator = standalone/authenticator = nginx/' \
/etc/letsencrypt/renewal/YOUR_DOMAIN.conf
This requires port 80 to be reachable from the internet.
Option B: Firewall Pre/Post Hooks
Temporarily open port 80 during the ACME challenge:
# Pre-hook: open port 80
sudo tee /etc/letsencrypt/renewal-hooks/pre/open-http.sh > /dev/null << 'EOF'
#!/bin/bash
iptables -I INPUT 5 -p tcp --dport 80 -j ACCEPT
logger -t certbot-firewall "Opened port 80 for ACME challenge"
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/pre/open-http.sh
# Post-hook: close port 80
sudo tee /etc/letsencrypt/renewal-hooks/post/close-http.sh > /dev/null << 'EOF'
#!/bin/bash
iptables -D INPUT -p tcp --dport 80 -j ACCEPT
logger -t certbot-firewall "Closed port 80 after ACME challenge"
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/close-http.sh
The rule number in -I INPUT 5 should place the ACCEPT rule before any DROP
rules in your chain. Adjust the number based on your iptables layout.
Verify Renewal Works
sudo certbot renew --dry-run
Important Notes
- LE certs expire every 90 days. Certbot's systemd timer handles automatic renewal (runs twice daily, only renews within 30 days of expiry).
- The deploy hook uses
systemctl restart asteriskwhich briefly drops all active calls. Schedule renewals during off-hours if this is a concern, or accept the brief interruption every 60-90 days. - If you have multiple domains, certbot will only trigger the deploy hook for the specific domain that renewed.
User Notes
No notes yet. Be the first to contribute a tip or example.
Contribute a note
Share a tip, gotcha, or practical example. Keep it under 2000 characters. No questions (use the Asterisk community forums for support). Wrap code in backticks.