Let's Encrypt TLS Certificates for Asterisk PJSIP

Security Asterisk 18+ -- Last reviewed 2026-03-29 tls certificates pjsip letsencrypt Found this useful? Upvote it. ×

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

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.

Moderated before publishing. Email never shown.

Related Snippets