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:
-
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.
-
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.
-
Temporary services or test environments
You can bring up HTTPS without creating DNS records first.
-
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:
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:
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:
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:
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:
[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:
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:
# 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:
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:
