Implementing ACME for NATS on Ubuntu
This guide provides step-by-step instructions for automating certificate management with ACME for NATS running directly on Ubuntu servers.
Prerequisites
- Ubuntu server with NATS installed and running
- Administrative (sudo) access
- Domain name pointing to your NATS server (or accessible via DNS)
- Basic familiarity with terminal commands
1. Install Certbot
Certbot is a popular ACME client that works well on Ubuntu:
sudo apt update
sudo apt install certbot
For DNS validation plugins (optional but recommended):
# For Cloudflare
sudo apt install python3-certbot-dns-cloudflare
# For Route53
sudo apt install python3-certbot-dns-route53
# For other providers
# sudo apt install python3-certbot-dns-<provider>
2. Obtain Initial Certificates using DNS Validation
Since NATS may not be publicly accessible over HTTP, DNS validation is often the best approach:
Manual DNS Validation
sudo certbot certonly --manual --preferred-challenges dns \
--agree-tos --email your-email@example.com \
-d nats.yourdomain.com
During this process, you'll need to create specific TXT records in your DNS. Follow the prompts and verify the records are properly set.
Automated DNS Validation (Recommended for Production)
If using Cloudflare (adjust for your provider):
- Create API credentials file:
sudo mkdir -p /etc/letsencrypt/dns-credentials
sudo nano /etc/letsencrypt/dns-credentials/cloudflare.ini
- Add your Cloudflare API token:
dns_cloudflare_api_token = your_cloudflare_api_token
- Secure the credentials:
sudo chmod 600 /etc/letsencrypt/dns-credentials/cloudflare.ini
- Run Certbot with the DNS plugin:
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/dns-credentials/cloudflare.ini \
--agree-tos --email your-email@example.com \
-d nats.yourdomain.com
3. Configure NATS to Use the Certificates
Edit your NATS configuration file (typically /etc/nats/nats-server.conf or similar):
tls {
cert_file: "/etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem"
key_file: "/etc/letsencrypt/live/nats.yourdomain.com/privkey.pem"
timeout: 2
}
For more advanced configurations:
tls {
cert_file: "/etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem"
key_file: "/etc/letsencrypt/live/nats.yourdomain.com/privkey.pem"
ca_file: "/etc/letsencrypt/live/nats.yourdomain.com/chain.pem"
verify: true
timeout: 2
cipher_suites: [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
curve_preferences: [
"CurveP256",
"CurveP384",
"CurveP521"
]
}
Restart NATS to apply the changes:
sudo systemctl restart nats
4. Create a Certificate Renewal Script
Create a script to handle certificate renewal and NATS restart:
sudo nano /usr/local/bin/renew-nats-cert.sh
Add the following content:
#!/bin/bash
# Set variables
DOMAIN="nats.yourdomain.com"
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
LOG_FILE="/var/log/nats-cert-renewal.log"
NATS_SERVICE="nats"
# Log function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
echo "$1"
}
# Create log file if it doesn't exist
touch "$LOG_FILE"
# Backup current certificates
if [ -f "$CERT_DIR/fullchain.pem" ]; then
BACKUP_DIR="/etc/letsencrypt/backup/$(date '+%Y%m%d%H%M%S')"
mkdir -p "$BACKUP_DIR"
cp "$CERT_DIR"/*.pem "$BACKUP_DIR/"
log "Backed up current certificates to $BACKUP_DIR"
fi
# Renew certificates
log "Starting certificate renewal process"
certbot renew --quiet
# Check if renewal was successful by comparing certificate modification times
if [ -f "$CERT_DIR/fullchain.pem" ]; then
CERT_MODIFIED=$(stat -c %Y "$CERT_DIR/fullchain.pem")
CURRENT_TIME=$(date +%s)
# If certificate was modified in the last hour, restart NATS
if [ $((CURRENT_TIME - CERT_MODIFIED)) -lt 3600 ]; then
log "Certificate renewed successfully, restarting NATS"
systemctl restart "$NATS_SERVICE"
# Verify NATS is running
sleep 2
if systemctl is-active --quiet "$NATS_SERVICE"; then
log "NATS restarted successfully"
else
log "ERROR: NATS failed to restart! Manual intervention required"
# Send alert (optional)
# mail -s "NATS Certificate Renewal Failed" admin@yourdomain.com < "$LOG_FILE"
fi
else
log "No new certificates found, renewal not needed at this time"
fi
else
log "ERROR: Certificate files not found after renewal attempt"
fi
Make the script executable:
sudo chmod +x /usr/local/bin/renew-nats-cert.sh
5. Set Up a Cron Job for Automated Renewal
Add a cron job to run the renewal script:
sudo crontab -e
Add this line to run the script twice daily (recommended by Let's Encrypt):
0 0,12 * * * /usr/local/bin/renew-nats-cert.sh
6. Fix Permissions for NATS to Access Certificates
Let's Encrypt typically creates certificates owned by root with restrictive permissions. Ensure NATS can read them:
# Determine the user NATS runs as
NATS_USER=$(ps -ef | grep nats-server | grep -v grep | awk '{print $1}')
# Add NATS user to certbot group
sudo usermod -a -G certbot "$NATS_USER"
# Adjust permissions on certificate directories
sudo chmod 750 /etc/letsencrypt/live
sudo chmod 750 /etc/letsencrypt/archive
# Ensure certificate files are readable
sudo chmod 640 /etc/letsencrypt/live/nats.yourdomain.com/*.pem
sudo chmod 640 /etc/letsencrypt/archive/nats.yourdomain.com/*.pem
7. Test the Setup
Manually test the renewal process:
sudo /usr/local/bin/renew-nats-cert.sh
And verify NATS is running with the correct certificates:
# Check if NATS is running
systemctl status nats
# Test TLS connectivity (using nats CLI if installed)
nats server info -s tls://nats.yourdomain.com:4222 --tlsverify
You can also verify the certificate expiration date:
openssl x509 -dates -noout -in /etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem
8. Setup Client Connections
Update your NATS clients to use TLS when connecting:
Go Example
// Connect to NATS with TLS
nc, err := nats.Connect("tls://nats.yourdomain.com:4222",
nats.RootCAs("/path/to/ca.pem"), // Optional if using a widely trusted CA
nats.ClientCert("/path/to/cert.pem", "/path/to/key.pem"), // Optional for mutual TLS
)
JavaScript Example
const NATS = require('nats');
const fs = require('fs');
// Connect to NATS with TLS
const nc = await NATS.connect({
servers: "tls://nats.yourdomain.com:4222",
tls: {
caFile: "/path/to/ca.pem", // Optional if using a widely trusted CA
certFile: "/path/to/cert.pem", // Optional for mutual TLS
keyFile: "/path/to/key.pem", // Optional for mutual TLS
}
});
Troubleshooting
Certificate Renewal Issues
- Check Certbot logs:
sudo cat /var/log/letsencrypt/letsencrypt.log
- Test renewal in dry-run mode:
sudo certbot renew --dry-run
NATS Connection Issues
- Verify certificate permissions:
ls -la /etc/letsencrypt/live/nats.yourdomain.com/
- Check NATS server logs:
sudo journalctl -u nats
- Test TLS connection directly:
openssl s_client -connect nats.yourdomain.com:4222 -showcerts
Security Best Practices
-
Rate limiting: Configure your firewall to rate-limit connections to your NATS server.
-
Mutual TLS: Consider implementing mutual TLS (mTLS) for client authentication.
-
Authorization: Set up proper user/account authorization in NATS beyond TLS.
-
Monitoring: Implement monitoring for certificate expiration and NATS availability.
-
Regular audits: Periodically review your security settings and certificate status.
Appendix: Sample Systemd Service File
If you need to create a systemd service file for NATS:
sudo nano /etc/systemd/system/nats.service
[Unit]
Description=NATS Server
Documentation=https://docs.nats.io/
After=network.target
[Service]
ExecStart=/usr/local/bin/nats-server --config /etc/nats/nats-server.conf
ExecReload=/bin/kill -s HUP $MAINPID
Type=simple
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable nats
sudo systemctl start nats
This guide provides a comprehensive approach to implementing ACME-based certificate management for NATS on Ubuntu. For specific environments or requirements, adjustments may be necessary.