Skip to content

Instantly share code, notes, and snippets.

@SeverinAlexB
Created January 23, 2026 10:28
Show Gist options
  • Select an option

  • Save SeverinAlexB/3f2a422362c0d0294dcb496221b43d05 to your computer and use it in GitHub Desktop.

Select an option

Save SeverinAlexB/3f2a422362c0d0294dcb496221b43d05 to your computer and use it in GitHub Desktop.
Let's encrypt IP cert guide - Ubuntu 24 + nginx

Let's Encrypt IP Address Certificate Setup Guide (Ubuntu 24.04)

This guide explains how to obtain and configure a Let's Encrypt TLS certificate for a bare IP address on Ubuntu 24.04 LTS.

Background

As of January 2026, Let's Encrypt offers IP address certificates in general availability. These certificates:

  • Are only available via the shortlived profile (~6-day validity)
  • Require HTTP-01 or TLS-ALPN-01 validation (DNS-01 is not supported for IP addresses)
  • Do not include CRL/OCSP URLs (per CA/Browser Forum Baseline Requirements for <10 day certs)

Prerequisites

  • Ubuntu 24.04 LTS server
  • Public IPv4 or IPv6 address
  • Port 80 accessible from the internet (for HTTP-01 challenge)
  • Root or sudo access

Step 1: Install nginx

apt update && apt install -y nginx

Verify nginx is running and accessible on port 80:

curl -I http://YOUR_IP_ADDRESS

Step 2: Install lego ACME Client

As of January 2026, certbot does not yet support IP address certificates. Use lego instead:

curl -sL https://github.com/go-acme/lego/releases/download/v4.31.0/lego_v4.31.0_linux_amd64.tar.gz \
  | tar xz -C /usr/local/bin lego

# Verify installation
lego --version

Step 3: Obtain the IP Certificate

Create the lego directory and request the certificate:

mkdir -p /etc/lego

lego --accept-tos \
  --http \
  --http.webroot /var/www/html \
  --domains YOUR_IP_ADDRESS \
  --path /etc/lego \
  --disable-cn \
  run --profile shortlived

Important flags:

Flag Purpose
--accept-tos Accept Let's Encrypt terms of service
--http Use HTTP-01 challenge
--http.webroot /var/www/html Write challenge files to nginx's webroot
--domains YOUR_IP_ADDRESS The IP address to certify
--disable-cn Don't put IP in Common Name (required for IP certs)
--profile shortlived Request shortlived profile (required for IP certs)

On success, certificates are saved to /etc/lego/certificates/:

/etc/lego/certificates/YOUR_IP_ADDRESS.crt      # Certificate + chain
/etc/lego/certificates/YOUR_IP_ADDRESS.key      # Private key
/etc/lego/certificates/YOUR_IP_ADDRESS.issuer.crt  # Issuer certificate

Step 4: Configure nginx for HTTPS

Edit /etc/nginx/sites-available/default:

# HTTP server - redirect to HTTPS (except ACME challenges)
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # Allow ACME challenge requests for certificate renewal
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    # Redirect all other HTTP traffic to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS server
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    # SSL certificate from Let's Encrypt (via lego)
    ssl_certificate /etc/lego/certificates/YOUR_IP_ADDRESS.crt;
    ssl_certificate_key /etc/lego/certificates/YOUR_IP_ADDRESS.key;

    # Modern SSL configuration (TLS 1.2+)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # SSL session settings
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }
}

Remember to replace YOUR_IP_ADDRESS with your actual IP address.

Test and reload nginx:

nginx -t && systemctl reload nginx

Step 5: Set Up Automatic Renewal

Since shortlived certificates are only valid for ~6 days, automatic renewal is critical.

Create /etc/cron.d/lego-renew:

# Renew Let's Encrypt IP certificate twice daily
# Renew when 2 days or less remain on certificate

15 3,15 * * * root /usr/local/bin/lego --accept-tos --http --http.webroot /var/www/html --domains YOUR_IP_ADDRESS --path /etc/lego --disable-cn renew --days 2 --profile shortlived --renew-hook "systemctl reload nginx" >> /var/log/lego-renew.log 2>&1

Set correct permissions:

chmod 644 /etc/cron.d/lego-renew

Step 6: Verify the Setup

Test HTTP redirect:

curl -I http://YOUR_IP_ADDRESS

Expected output includes:

HTTP/1.1 301 Moved Permanently
Location: https://YOUR_IP_ADDRESS/

Test HTTPS and certificate:

curl -sv https://YOUR_IP_ADDRESS 2>&1 | grep -E "SSL connection|subject:|issuer:|expire"

Expected output:

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
*  subject: [NONE]
*  issuer: C=US; O=Let's Encrypt; CN=YE2
*  expire date: <approximately 6-7 days from now>

Verify certificate details:

echo | openssl s_client -connect YOUR_IP_ADDRESS:443 2>/dev/null \
  | openssl x509 -noout -dates -issuer -ext subjectAltName

Expected output:

notBefore=<issue date>
notAfter=<expiry date ~6-7 days later>
issuer=C = US, O = Let's Encrypt, CN = YE2
X509v3 Subject Alternative Name: critical
    IP Address:YOUR_IP_ADDRESS

Troubleshooting

"CSR contains IP address in Common Name"

You forgot --disable-cn. IP addresses cannot be in the certificate's Common Name field, only in the Subject Alternative Name (SAN).

"acme: error: 400 :: urn:ietf:params:acme:error:rejectedIdentifier"

The shortlived profile is required for IP certificates. Make sure you're using --profile shortlived after the run or renew subcommand.

Certificate renewal fails

  1. Check that port 80 is accessible from the internet
  2. Verify the webroot path matches nginx's root directive
  3. Check logs: cat /var/log/lego-renew.log
  4. Test manually:
    lego --accept-tos --http --http.webroot /var/www/html \
      --domains YOUR_IP_ADDRESS --path /etc/lego --disable-cn \
      renew --days 2 --profile shortlived

nginx fails to start after certificate expires

If the certificate expires before renewal, nginx won't start. Obtain a new certificate:

# Remove old certificate
rm /etc/lego/certificates/YOUR_IP_ADDRESS.*

# Temporarily configure nginx for HTTP only, then:
lego --accept-tos --http --http.webroot /var/www/html \
  --domains YOUR_IP_ADDRESS --path /etc/lego --disable-cn \
  run --profile shortlived

# Restore HTTPS config and reload
systemctl reload nginx

Important Notes

  1. Short validity period: These certificates expire in ~6 days. Ensure your renewal cron job is working reliably.

  2. No OCSP/CRL: Short-lived certificates don't include revocation information. If compromised, you must wait for expiry or manually replace the certificate.

  3. Rate limits: Let's Encrypt has rate limits. For IP certificates:

    • 300 new orders per account per 3 hours
    • Failed validations count against limits
  4. IPv6 support: IPv6 addresses are also supported. Use the full IPv6 address format.

  5. Why lego instead of certbot?: As of January 2026, certbot (v5.2.2) has client-side validation that rejects IP addresses. lego (v4.31.0+) fully supports IP certificates.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment