6.2.b. Secure with Let's Encrypt
Goal
Obtain a trusted SSL certificate from Let’s Encrypt using DNS-01 challenge via DuckDNS, eliminating browser security warnings and enabling secure HTTPS connections to your application.
What you’ll learn:
- How Let’s Encrypt’s DNS-01 challenge works for domain verification
- How to install and configure Certbot with DNS plugins
- How to configure Nginx for HTTPS with automatic certificate management
- Best practices for certificate renewal automation
Prerequisites
Before starting, ensure you have:
- ✓ Completed Tutorial 06 (Nginx Reverse Proxy) with VM running
- ✓ Completed Tutorial 06.2a (DuckDNS Setup) with subdomain configured
- ✓ DuckDNS token saved securely
- ✓ SSH access to your VM (
ssh azureuser@<VM_IP>)- ✓ Azure CLI logged in (
az login)
Exercise Steps
Overview
- Understand the DNS-01 Challenge
- Open Port 443 for HTTPS
- Update DuckDNS with VM IP
- Install Certbot and DuckDNS Plugin
- Create DuckDNS Credentials File
- Request Let’s Encrypt Certificate
- Install Certificate into Nginx
- Set Up Automatic Renewal
- Test Your HTTPS Setup
Step 1: Understand the DNS-01 Challenge
Before requesting a certificate, it’s important to understand how Let’s Encrypt verifies domain ownership. The DNS-01 challenge is ideal for scenarios where you need wildcard certificates or when your server isn’t publicly accessible on port 80.
Let’s Encrypt offers several challenge types to verify you control a domain:
- HTTP-01: Place a file on your web server (requires port 80)
- DNS-01: Create a TXT record in your DNS (works for any setup)
- TLS-ALPN-01: Respond on port 443 with a special certificate
We use DNS-01 because:
- DuckDNS has API support for automatic TXT record creation
- Works even if port 80 is blocked
- Can issue wildcard certificates
- Doesn’t require changes to your web server during verification
The process works like this:
- Certbot requests a challenge from Let’s Encrypt
- Let’s Encrypt provides a random token
- Certbot creates a TXT record:
_acme-challenge.yourdomain.duckdns.org - Let’s Encrypt queries DNS for this record
- If the token matches, certificate is issued
ℹ Concept Deep Dive
DNS-01 is particularly useful because it proves you control the domain at the DNS level, which is a stronger proof of ownership than just having access to a web server. The tradeoff is that it requires API access to your DNS provider, which is why we use DuckDNS - it provides a simple API for this purpose.
✓ Quick check: You understand that certbot will automatically update your DuckDNS TXT record
Step 2: Open Port 443 for HTTPS
Your VM’s Network Security Group currently only allows HTTP traffic on port 80. HTTPS requires port 443 to be open. This step configures Azure’s firewall to allow encrypted traffic.
Open a terminal on your local machine
Run the following Azure CLI command to open port 443:
az vm open-port \ --resource-group hellojava-reverseproxy-rg \ --name hellojava-reverseproxy-vm \ --port 443 \ --priority 310Verify the port is open:
az network nsg rule list \ --resource-group hellojava-reverseproxy-rg \ --nsg-name hellojava-reverseproxy-vmNSG \ --output tableYou should see rules for ports 22, 80, and 443.
ℹ Concept Deep Dive
Azure Network Security Groups (NSGs) act as virtual firewalls. Each rule has a priority number - lower numbers are evaluated first. We use priority 310 for HTTPS (after 300 for HTTP) to maintain a logical ordering.
⚠ Common Mistakes
- Using the wrong resource group name (check with
az group list)- Forgetting the NSG name includes “NSG” suffix
✓ Quick check: The command completes without errors and port 443 appears in the rule list
Step 3: Update DuckDNS with VM IP
DuckDNS needs to point to your VM’s current IP address. While you may have set this during Tutorial 06.2a, it’s good practice to verify and update it before requesting a certificate.
Get your VM’s public IP address:
az vm show -d \ --resource-group hellojava-reverseproxy-rg \ --name hellojava-reverseproxy-vm \ --query publicIps -o tsvNote this IP address.
Update DuckDNS with the IP (replace
SUBDOMAINandTOKEN):curl "https://www.duckdns.org/update?domains=SUBDOMAIN&token=TOKEN&ip=YOUR_VM_IP"You should see
OKin the response.Verify DNS resolution:
dig +short SUBDOMAIN.duckdns.orgThis should return your VM’s IP address.
ℹ Concept Deep Dive
DNS propagation is usually instant with DuckDNS, but can take up to 5 minutes in some cases. If
digreturns the wrong IP, wait a few minutes and try again. Let’s Encrypt will query multiple DNS servers during validation, so correct DNS is essential.✓ Quick check:
digreturns your VM’s current public IP address
Step 4: Install Certbot and DuckDNS Plugin
Certbot is the official Let’s Encrypt client. We’ll install it as a snap package along with the DuckDNS DNS plugin that enables automatic DNS record management.
SSH into your VM:
ssh azureuser@YOUR_VM_IPInstall Certbot via snap:
sudo snap install --classic certbotCreate a symlink to make certbot available system-wide:
sudo ln -sf /snap/bin/certbot /usr/bin/certbotEnable snap plugin trust:
sudo snap set certbot trust-plugin-with-root=okInstall the DuckDNS plugin:
sudo snap install certbot-dns-duckdnsConnect the plugin to Certbot:
sudo snap connect certbot:plugin certbot-dns-duckdnsVerify the installation:
certbot --version certbot pluginsYou should see
dns-duckdnsin the plugins list.
ℹ Concept Deep Dive
We use snap instead of apt because snap provides the latest Certbot version with automatic updates. The
trust-plugin-with-root=oksetting allows Certbot to use third-party DNS plugins that need root access to create credentials files.⚠ Common Mistakes
- Forgetting to connect the plugin (certbot won’t see it)
- Missing the symlink (certbot command not found)
✓ Quick check:
certbot pluginsshowsdns-duckdnsin the list
Step 5: Create DuckDNS Credentials File
Certbot needs your DuckDNS token to create TXT records. We’ll create a secure credentials file that only root can read.
Create the credentials file (replace
YOUR_TOKENwith your actual token):sudo mkdir -p /etc/letsencrypt sudo tee /etc/letsencrypt/duckdns-credentials << EOF dns_duckdns_token = YOUR_TOKEN EOFSecure the file permissions:
sudo chmod 600 /etc/letsencrypt/duckdns-credentialsVerify the file was created correctly:
sudo cat /etc/letsencrypt/duckdns-credentialsYou should see your token in the output.
ℹ Concept Deep Dive
The credentials file must be in
/etc/letsencrypt/because Certbot runs as a snap with restricted filesystem access. The snap cannot read files from/root/or other user directories. The 600 permission ensures only root can read the file.⚠ Common Mistakes
- Putting credentials in
/root/(snap can’t access it)- Wrong permissions (Certbot will refuse to use world-readable credentials)
- Spaces around the
=sign (use exactly:dns_duckdns_token = TOKEN)✓ Quick check: File exists with correct permissions (600) and contains your token
Step 6: Request Let’s Encrypt Certificate
Now we’ll request the actual certificate. Certbot will contact Let’s Encrypt, create the DNS TXT record via DuckDNS, and wait for validation.
Run the certificate request (replace
SUBDOMAINandEMAIL):sudo certbot certonly \ --authenticator dns-duckdns \ --dns-duckdns-credentials /etc/letsencrypt/duckdns-credentials \ --dns-duckdns-propagation-seconds 90 \ -d SUBDOMAIN.duckdns.org \ --agree-tos \ --email YOUR_EMAIL \ --non-interactiveWait for the process to complete (about 2 minutes).
Verify the certificate was obtained:
sudo ls -la /etc/letsencrypt/live/You should see a directory named
SUBDOMAIN.duckdns.org.
ℹ Concept Deep Dive
The
--dns-duckdns-propagation-seconds 90tells Certbot to wait 90 seconds after creating the TXT record before asking Let’s Encrypt to verify. This allows time for DNS propagation. The certificate files are stored in/etc/letsencrypt/live/DOMAIN/with symlinks to the current versions.⚠ Common Mistakes
- Wrong credentials path (use
/etc/letsencrypt/duckdns-credentials)- Insufficient propagation time (60 seconds sometimes fails)
- Typo in domain name
- Token incorrect in credentials file
⚠ Transient DNS Failures
If you see errors like “SERVFAIL” or “DNS timeout”, don’t panic. DNS-01 challenges can occasionally fail due to temporary DNS resolution issues. Simply wait a minute and run the command again - it usually succeeds on retry. This is normal and happens even in production environments.
✓ Quick check: Certificate directory exists in
/etc/letsencrypt/live/
Step 7: Install Certificate into Nginx
The certificate is obtained but Nginx doesn’t know about it yet. Certbot can automatically configure Nginx to use the certificate and redirect HTTP to HTTPS.
Install the certificate into Nginx (replace
SUBDOMAIN):sudo certbot install \ --nginx \ --cert-name SUBDOMAIN.duckdns.org \ --redirect \ --non-interactiveVerify Nginx configuration was updated:
sudo nginx -tYou should see “syntax is ok” and “test is successful”.
Check the Nginx configuration:
sudo cat /etc/nginx/sites-available/hellojavaYou should see SSL directives and a redirect from port 80 to 443.
ℹ Concept Deep Dive
The
--redirectflag tells Certbot to configure Nginx to redirect all HTTP traffic to HTTPS. Certbot modifies your existing Nginx configuration to add the SSL certificate paths, enable TLS, and set up secure defaults. This is safer than manually editing the configuration.✓ Quick check: Nginx configuration test passes and shows SSL settings
Step 8: Set Up Automatic Renewal
Let’s Encrypt certificates expire after 90 days. Certbot automatically renews them, but we need to ensure Nginx reloads when certificates are renewed.
Create the renewal hooks directory:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deployCreate a hook script to reload Nginx:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'EOF' #!/bin/bash systemctl reload nginx EOFMake the script executable:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shTest the renewal process (dry run):
sudo certbot renew --dry-runYou should see “Congratulations, all simulated renewals succeeded!”
ℹ Concept Deep Dive
Certbot sets up a systemd timer that runs twice daily to check for certificates needing renewal. It only renews certificates within 30 days of expiry. The deploy hook runs after successful renewal to reload Nginx with the new certificate.
✓ Quick check: Dry run completes successfully
Step 9: Test Your HTTPS Setup
Time to verify that everything works correctly. We’ll test HTTPS access, HTTP redirect, and certificate validity from outside the VM.
Exit the SSH session:
exitTest HTTPS access (replace
SUBDOMAIN):curl -s https://SUBDOMAIN.duckdns.org/actuator/healthYou should see
{"status":"UP"}.Test HTTP to HTTPS redirect:
curl -s -o /dev/null -w "%{http_code}" http://SUBDOMAIN.duckdns.org/You should see
301(redirect).Check certificate details:
echo | openssl s_client -connect SUBDOMAIN.duckdns.org:443 2>/dev/null | openssl x509 -noout -issuer -subject -datesYou should see:
- Issuer containing “Let’s Encrypt”
- Subject with your domain
- Valid dates (90 days from now)
Test in a browser:
Open
https://SUBDOMAIN.duckdns.org/in your browser.You should see:
- Padlock icon (secure connection)
- No certificate warnings
- Your HelloJava application
✓ Success indicators:
- HTTPS returns 200 with health check response
- HTTP returns 301 redirect to HTTPS
- Certificate issued by Let’s Encrypt
- Browser shows padlock without warnings
- Application works over HTTPS
✓ Final verification checklist:
- ☐ Port 443 open in Azure NSG
- ☐ DuckDNS points to correct IP
- ☐ Certbot and plugin installed
- ☐ Credentials file in correct location with proper permissions
- ☐ Certificate obtained and stored
- ☐ Nginx configured for SSL
- ☐ Auto-renewal configured and tested
- ☐ HTTPS accessible from browser
Optional: Add HSTS Header for Maximum Security
HTTP Strict Transport Security (HSTS) tells browsers to always use HTTPS for your domain, preventing downgrade attacks. This is optional but recommended for production.
SSH into your VM:
ssh azureuser@YOUR_VM_IPEdit the Nginx configuration:
sudo nano /etc/nginx/sites-available/hellojavaAdd the HSTS header inside the
serverblock that listens on port 443 (the SSL block):add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Place it after the
ssl_certificatelines.Test the configuration:
sudo nginx -tReload Nginx:
sudo systemctl reload nginxVerify the header is present:
curl -sI https://SUBDOMAIN.duckdns.org/ | grep -i strictYou should see
Strict-Transport-Security: max-age=31536000; includeSubDomains.
ℹ Concept Deep Dive
HSTS prevents SSL stripping attacks where an attacker intercepts HTTP requests before they redirect to HTTPS. Once a browser sees the HSTS header, it will always use HTTPS for that domain - even if the user types
http://. Themax-age=31536000means browsers will remember this for one year. Use with caution in testing environments since it’s hard to undo!
Common Issues
If you encounter problems:
Certificate request fails with “unauthorized”:
- Verify your DuckDNS token is correct in the credentials file
- Check the credentials file is at
/etc/letsencrypt/duckdns-credentials- Ensure file permissions are 600
- Try increasing propagation time to 120 seconds
DNS timeout or SERVFAIL during certificate request:
- This is often transient - simply wait 1-2 minutes and try again
- DNS-01 challenges require Let’s Encrypt’s servers to query DuckDNS, and temporary network issues can cause failures
- Retrying usually succeeds - this is normal, not a configuration error
- Check that DuckDNS is accessible:
curl https://www.duckdns.org- If it fails repeatedly, try increasing propagation time to 120 seconds
Nginx won’t start after certificate install:
- Check syntax:
sudo nginx -t- View error log:
sudo tail /var/log/nginx/error.log- Verify certificate files exist in
/etc/letsencrypt/live/Browser still shows “Not Secure”:
- Clear browser cache
- Check you’re using
https://nothttp://- Verify the domain matches the certificate
Still stuck? Check Certbot logs:
sudo cat /var/log/letsencrypt/letsencrypt.log
Summary
You’ve successfully secured your application with a Let’s Encrypt certificate which:
- ✓ Provides trusted HTTPS with no browser warnings
- ✓ Automatically redirects HTTP to HTTPS
- ✓ Renews automatically before expiry
- ✓ Uses modern TLS configuration
Key takeaway: Let’s Encrypt with DNS-01 challenge is the gold standard for free, automated SSL certificates. This same pattern works for any domain - not just DuckDNS - as long as you have API access to create TXT records. In production, you’d use this with your own domain and a DNS provider like Cloudflare or Azure DNS.
Going Deeper (Optional)
Want to explore more?
- Research how ACME protocol works under the hood
- Try issuing a wildcard certificate for
*.SUBDOMAIN.duckdns.org- Compare HTTP-01 vs DNS-01 challenge types
- Investigate Certificate Transparency logs
- Add HSTS preloading for maximum security
Clean Up
When you’re done with this tutorial, you can either:
Keep the VM: Your HTTPS setup is production-ready!
Delete resources:
az group delete --name hellojava-reverseproxy-rg --yes --no-wait
ℹ Note
Deleting the VM removes the certificate, but your DuckDNS subdomain remains. You can reuse it for future projects.
Done! 🎉
Excellent work! You’ve learned how to obtain and install trusted SSL certificates using Let’s Encrypt’s DNS-01 challenge. Your application is now secured with industry-standard encryption and will automatically maintain its certificate. This is the same certificate infrastructure used by millions of websites worldwide!