m.ac

Get Let's Encrypt certificates for IP addresses with acme.sh

434 words2 min read

This guide uses acme.sh to obtain Let's Encrypt TLS certificates for IP addresses.

For a long time, Let's Encrypt issued certificates only for domain names. After several months of testing, Let's Encrypt made six-day and IP address certificates generally available on January 15, 2026.

#1. Why issue certificates for IP addresses

Sometimes a domain name is unnecessary, but HTTPS is still required:

  1. DNS over HTTPS (DoH) services that operate independently of domain resolution

    Running DoH directly on an IP address avoids the old philosophical headache: doing an insecure DNS lookup before opening a secure DNS connection.

  2. Default server blocks that should not expose real names

    A default HTTPS vhost can present an IP certificate instead of leaking one of your real hostnames.

  3. Temporary services or test environments

    You can bring up HTTPS without creating DNS records first.

  4. Avoiding domain exposure in Certificate Transparency logs

    If a service does not need a domain name, an IP certificate avoids publishing that domain name in public CT logs.

#2. Prerequisites

The commands below use sudo. If you prefer a root shell, run sudo -i first and drop sudo from the commands.

Update acme.sh first:

bash
acme.sh --upgrade

IP address certificates can only use http-01 or tls-alpn-01 validation. Make sure your firewall allows public access to TCP port 80 and TCP/UDP port 443.

#3. Configure the NGINX default server for port 80

Set up a default NGINX server block on port 80.

On Debian or Ubuntu systems with NGINX installed, replace /etc/nginx/sites-available/default with this configuration:

nginx
server {
    # Listen on port 80 for all IPv4 and IPv6 addresses
    listen 80 default_server;
    listen [::]:80 default_server;

    # Match all domain names
    server_name _;

    # Merge Let's Encrypt and SSL verification path configuration
    location ~ ^/.well-known/(acme-challenge|pki-validation)/ {
        add_header Content-Type text/plain;
        root /var/www/letsencrypt;
    }

    # Redirect all other HTTP requests to HTTPS using 301 permanent redirect
    location / {
        return 301 https://$host$request_uri;
    }
}

Create the required directories and reload NGINX:

bash
sudo mkdir -p /var/www/letsencrypt
sudo mkdir -p /etc/nginx/ssl
sudo nginx -t
sudo nginx -s reload

#4. Issue the IP address certificate with acme.sh

Assume your server's IP addresses are 192.0.2.2 and 2001:db8::2, and request the shortlived profile:

bash
acme.sh --issue --server letsencrypt -d 192.0.2.2 -d 2001:db8::2 \
  -w /var/www/letsencrypt \
  --certificate-profile shortlived \
  --days 3

The shortlived profile is required for IP address certificates. Let's Encrypt limits these certificates to 160 hours, so acme.sh needs to check renewal more often than it would for a normal 90-day certificate. --days 3 checks every 3 days. A value of 4 or 5 can still work, but 6 or higher is asking for an avoidable expiry page.

If issuance succeeds, you will see output like this:

text
[Wed Dec 17 05:46:28 AM UTC 2025] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Wed Dec 17 05:46:28 AM UTC 2025] Multi domain='IP:192.0.2.2,IP:2001:db8::2'
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain='192.0.2.2'
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain='2001:db8::2'
[Wed Dec 17 05:46:30 AM UTC 2025] Verifying: 192.0.2.2
[Wed Dec 17 05:46:31 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:34 AM UTC 2025] Success
[Wed Dec 17 05:46:34 AM UTC 2025] Verifying: 2001:db8::2
[Wed Dec 17 05:46:35 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:38 AM UTC 2025] Success
[Wed Dec 17 05:46:38 AM UTC 2025] Verification finished, beginning signing.
[Wed Dec 17 05:46:38 AM UTC 2025] Let's finalize the order.
[Wed Dec 17 05:46:38 AM UTC 2025] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/blablablablablablablabla/blablablablablablablabla'
[Wed Dec 17 05:46:41 AM UTC 2025] Downloading cert.
[Wed Dec 17 05:46:41 AM UTC 2025] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/blablablablablablablabla'
[Wed Dec 17 05:46:42 AM UTC 2025] Cert success.
-----BEGIN CERTIFICATE-----
blablablablablablablablablablablablablablablablablablabla
-----END CERTIFICATE-----
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.cer
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert key is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.key
[Wed Dec 17 05:46:42 AM UTC 2025] The intermediate CA cert is in: /root/.acme.sh/192.0.2.2_ecc/ca.cer
[Wed Dec 17 05:46:42 AM UTC 2025] And the full-chain cert is in: /root/.acme.sh/192.0.2.2_ecc/fullchain.cer

Install the issued certificate into /etc/nginx/ssl:

bash
acme.sh --install-cert -d 192.0.2.2 \
  --key-file       /etc/nginx/ssl/ip.key  \
  --fullchain-file /etc/nginx/ssl/ip.crt \
  --ca-file        /etc/nginx/ssl/ip.ca.crt \
  --reloadcmd     "systemctl restart nginx"

#5. Configure the NGINX default server for port 443

After installing the certificate, add the default HTTPS server block to /etc/nginx/sites-available/default:

nginx
# HTTPS server block for all HTTPS requests
server {
    # Standard TLS listening
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    # HTTP/2 protocol support
    http2 on;

    # HTTP/3 QUIC protocol support
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    add_header X-Protocol $server_protocol always;

    # Match all domain names
    server_name _;
    return 403;

    # modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    ssl_certificate /etc/nginx/ssl/ip.crt;
    ssl_certificate_key /etc/nginx/ssl/ip.key;
}

Test the configuration and reload NGINX:

bash
sudo nginx -t
sudo nginx -s reload

After this is configured, https://192.0.2.2/ returns a 403 page. Check the certificate details and the Subject Alt Names field should show IP Address:

IP Certificate Works

Get Let's Encrypt certificates for IP addresses with acme.sh
Author
Xiufeng Guo
Published
Jan 15, 2026
Last updated
Jan 15, 2026
License
Short link
ADShare it the short way.URL shortener, file and text sharing, link in bio. Run by yours truly.S.EE →
Share