This week, I deployed a private n8n automation instance in Azure with a focus on security, auditability, and zero public exposure. Here’s how I solved the HTTPS challenge without storing credentials or opening ports unnecessarily.
Problem Statement
I needed to:
- Run n8n privately for internal automations
- Enable HTTPS for browser access and webhook security
- Use Let’s Encrypt for free TLS certs
- Avoid storing Azure credentials on the VM
- Keep the VM locked down with minimal exposure
Azure VM and NSG Setup
- Deployed Ubuntu VM with n8n running via systemd
- Configured Azure Network Security Group (NSG) to allow:
- Port 22 (SSH) and 443 (HTTPS) only
- Scoped to my static IP
- Temporarily opened port 80 for Let’s Encrypt HTTP challenge
SSL Issue: Nginx Serving Self-Signed Cert
Despite running Certbot successfully, openssl s_client
revealed:
verify error:num=18:self-signed certificate
issuer=CN = myn8n.southcentralus.cloudapp.azure.com
Nginx was still pointing to a self-signed cert:
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
Fix
Updated Nginx to use Certbot’s issued certs:
ssl_certificate /etc/letsencrypt/live/myn8n.southcentralus.cloudapp.azure.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myn8n.southcentralus.cloudapp.azure.com/privkey.pem;
Reloaded Nginx:
sudo nginx -t && sudo systemctl reload nginx
Using Managed Identity for NSG Rule Automation
To avoid storing Azure credentials:
- Enabled System Assigned Managed Identity on the VM
- Granted Network Contributor on the NSG resource
- Wrote a script that:
- Temporarily opens port 80
- Runs Certbot with HTTP challenge
- Closes port 80 immediately after
This allows (more) secure, credential-free NSG modification using Azure CLI authenticated via managed identity.
Disabling Certbot’s Systemd Timer
To ensure NSG rules are modified before renewal, I disabled Certbot’s default timer:
sudo systemctl disable certbot.timer
sudo systemctl stop certbot.timer
Instead, I use a cron job that runs the NSG rule script followed by Certbot:
0 3 * * * /home/n8n/scripts/renew-cert.sh >> /var/log/letsencrypt-renew.log 2>&1
Final Validation
Verified with:
openssl s_client -connect myn8n.southcentralus.cloudapp.azure.com:443 -servername myn8n.southcentralus.cloudapp.azure.com
Expected output:
Verify return code: 0 (ok)
issuer=/C=US/O=Let's Encrypt/...
Outcome
- HTTPS with valid cert
- No secrets stored on disk
- Minimal public exposure
- Fully automated renewals via cron
- NSG rule automation via managed identity
This setup is ideal for secure, internal automation workflows in Azure—no third-party dependencies, no credential leakage, and full auditability.