Let's Encrypt

How to Get Let's Encrypt SSL Certificates for IP Addresses with acme.sh

Learn how to issue free SSL/TLS certificates for IP addresses using acme.sh and Let's Encrypt. Includes NGINX configuration for HTTP/2 and HTTP/3 support.

4 min read
How to Get Let's Encrypt SSL Certificates for IP Addresses with acme.sh

Learn how to use acme.sh to obtain SSL certificates for IP addresses from Let's Encrypt.

For a long time, Let's Encrypt only supported certificate issuance for domain names. After several months of testing, Let's Encrypt now supports issuing certificates for IP addresses.

1. Why Issue Certificates for IP Addresses

There are several scenarios where a domain name is unnecessary, but HTTPS is still required:

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

    Providing DoH services directly via IP addresses avoids the philosophical dilemma of "performing an insecure DNS lookup to establish a secure connection."

  2. Hiding real domain names on default server blocks

    Exposing only the IP address on default server blocks keeps real domain names private and helps block certain unwanted crawlers.

  3. Temporary services or testing environments

    Quickly spinning up a service that requires HTTPS without the hassle of configuring DNS records.

  4. Avoiding domain exposure in Certificate Transparency Logs

    Some domain names are best kept out of public CT logs for privacy reasons.

2. Prerequisites

First, update acme.sh to the latest version:

acme.sh --upgrade

Since IP address certificates can only be validated through http-01 or tls-alpn-01 challenges, ensure your server's firewall allows public access to TCP port 80 and TCP/UDP port 443.

3. Configure the NGINX Default Server for Port 80

This section covers the NGINX configuration. We will set up a default server block listening on port 80.

On Debian or Ubuntu systems with NGINX installed, you can overwrite the /etc/nginx/sites-available/default file with the following configuration:

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;
    }
}

Then create the required directories and reload NGINX:

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

Assuming your server's IP addresses are 192.0.2.2 and 2001:db8::2 with profile shortlived:

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

Note that the shortlived profile is required for IP address certificates. Let's Encrypt limits IP certificate validity to 6.66666 days (160 hours), so acme.sh needs to check for renewal more frequently. Setting --days 3 ensures renewal checks occur every 3 days. You may set this value to 4 or 5, but avoid setting it to 6 or higher, as the certificate may expire before renewal.

Upon successful execution, you will see output similar to the following:

[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

Next, install the issued certificate to the /etc/nginx/ssl directory:

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, configure the default HTTPS server block. Add the following configuration to /etc/nginx/sites-available/default:

# HTTPS Server block - Handle 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:

sudo nginx -t
sudo nginx -s reload

Once everything is configured, visiting https://192.0.2.2/ will return a 403 error page. You can verify the certificate's Subject Alt Names field now displays IP Address:

On this page
Share this: