Русский

Obtaining a TLS Certificate Without Your Own Domain

5 min

What if you want to quickly put a web server on the Internet with a valid TLS certificate issued by Let's Encrypt, without registering a domain or dealing with DNS configuration?

sslip.io can help.


How sslip.io works

sslip.io is a free DNS service built around a simple idea: any subdomain containing an IP address automatically resolves to that address. There is no registration, DNS record configuration, or control panel: everything works through wildcard DNS.

sslip.io accepts IP addresses in three formats:

Format Domain Resolves to
Dots 186.246.5.239.sslip.io 186.246.5.239
Hyphens 186-246-5-239.sslip.io 186.246.5.239
Hex BAF605EF.sslip.io 186.246.5.239

Let's check all three using a real IP address:

$ host 186.246.5.239.sslip.io
186.246.5.239.sslip.io has address 186.246.5.239

$ host 186-246-5-239.sslip.io
186-246-5-239.sslip.io has address 186.246.5.239

$ host BAF605EF.sslip.io
BAF605EF.sslip.io has address 186.246.5.239

Arbitrary subdomains work too: sslip.io recognizes the IP address anywhere in the name:

$ host app.186.246.5.239.sslip.io
app.186.246.5.239.sslip.io has address 186.246.5.239

$ host www.186-246-5-239.sslip.io
www.186-246-5-239.sslip.io has address 186.246.5.239

IPv6 uses hyphens only, with a double colon (::) encoded as a double hyphen (--):

$ host 2a01-4f8-c012-1234--1.sslip.io
2a01-4f8-c012-1234--1.sslip.io has IPv6 address 2a01:4f8:c012:1234::1

nip.io is a similar service supporting the same formats. It is useful as a fallback if sslip.io hits rate limits, which we will discuss below:

$ host 186.246.5.239.nip.io
186.246.5.239.nip.io has address 186.246.5.239

Issuing a certificate: a step-by-step example

Below is an actual certificate issuance log from a fresh VPS running Ubuntu 24.04 at 186.246.5.239. It covers everything from connecting to the server to checking HTTPS.

1. Connect and install certbot

$ ssh root@186.246.5.239

root@msk-1-vm-87qh:~# apt-get update -qq && apt-get install -y -qq certbot
...
Setting up python3-acme (2.9.0-1) ...
Setting up python3-certbot (2.9.0-1) ...
Setting up certbot (2.9.0-1) ...

2. Issue the certificate

In --standalone mode, certbot starts a temporary HTTP server on port 80. Let's Encrypt connects to the domain, verifies that it points to this server, and issues a certificate:

root@msk-1-vm-87qh:~# certbot certonly --standalone --non-interactive \
  --agree-tos -m admin@example.com -d 186.246.5.239.nip.io

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for 186.246.5.239.nip.io

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/186.246.5.239.nip.io/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/186.246.5.239.nip.io/privkey.pem
This certificate expires on 2026-08-05.

Done: only a few seconds elapsed between running the command and receiving the certificate.

3. Check the certificate

root@msk-1-vm-87qh:~# openssl x509 -in /etc/letsencrypt/live/186.246.5.239.nip.io/fullchain.pem \
  -noout -subject -issuer -dates

subject=CN = 186.246.5.239.nip.io
issuer=C = US, O = Let's Encrypt, CN = E8
notBefore=May  7 11:28:32 2026 GMT
notAfter=Aug  5 11:28:31 2026 GMT

This is a valid Let's Encrypt certificate signed by the E8 intermediate CA. It is valid for 90 days, and certbot will renew it automatically through its timer.

4. Check HTTPS

Start a minimal HTTPS server using the new certificate, then check it with curl:

root@msk-1-vm-87qh:~# python3 -c "
import ssl, http.server
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(
    '/etc/letsencrypt/live/186.246.5.239.nip.io/fullchain.pem',
    '/etc/letsencrypt/live/186.246.5.239.nip.io/privkey.pem')
srv = http.server.HTTPServer(('0.0.0.0', 443), http.server.SimpleHTTPRequestHandler)
srv.socket = ctx.wrap_socket(srv.socket, server_side=True)
srv.handle_request()
" &

root@msk-1-vm-87qh:~# curl -s -o /dev/null -w "HTTP %{http_code}, TLS %{ssl_verify_result}\n" \
  https://186.246.5.239.nip.io/
HTTP 200, TLS 0

TLS 0 means that the certificate passed validation of its entire trust chain. A browser will open this address without warnings.


Rate limits: an sslip.io pitfall

The original plan was to issue the certificate for sslip.io, which most guides recommend as the primary service. However, certbot returned an error when I tried it on the same server:

root@msk-1-vm-87qh:~# certbot certonly --standalone --non-interactive \
  --agree-tos -m admin@example.com -d 186-246-5-239.sslip.io

Requesting a certificate for 186-246-5-239.sslip.io
An unexpected error occurred:
too many certificates (250000) already issued for "sslip.io"
in the last 168h0m0s, retry after 2026-05-07 12:26:42 UTC

250,000 certificates in one week: sslip.io is popular enough to regularly hit Let's Encrypt's limits. This error is why the example above uses nip.io instead of sslip.io. nip.io uses the same mechanism, but with a different domain and a separate rate-limit counter. Both services are included in the Public Suffix List, so Let's Encrypt treats them as different registered domains with independent counters.

The takeaway: do not depend on a single service. If sslip.io returns a rate-limit error, try nip.io, and vice versa.


Using the certificate with web servers

Caddy

Caddy obtains certificates automatically; specifying the domain is enough:

186.246.5.239.nip.io {
    reverse_proxy localhost:8000
}

On startup, Caddy completes the ACME challenge and begins serving HTTPS. Ports 80 and 443 must be reachable from outside: Let's Encrypt's HTTP-01 challenge connects to port 80, and the server must not be behind CGNAT or a firewall that blocks access.

Nginx

server {
    listen 443 ssl;
    server_name 186.246.5.239.nip.io;

    ssl_certificate     /etc/letsencrypt/live/186.246.5.239.nip.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/186.246.5.239.nip.io/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

Automating the first boot

The entire process fits into a cloud-init or bootstrap script, giving the server HTTPS on its first startup:

#!/usr/bin/env bash
set -euo pipefail

IP=$(curl -s ifconfig.me)
DOMAIN="$IP.nip.io"

apt-get update -qq && apt-get install -y -qq certbot
certbot certonly --standalone --non-interactive --agree-tos \
  -m admin@example.com -d "$DOMAIN"

echo "Сертификат готов: https://$DOMAIN"

Running your own wildcard DNS

sslip.io is open source and written in Go. If you do not want to depend on someone else's DNS servers and rate limits, you can run your own instance under a domain you control. Prebuilt binaries are available on the releases page.

A minimal startup sequence:

curl -sLO https://github.com/cunnie/sslip.io/releases/download/5.0.1/sslip.io-dns-server-linux-amd64
chmod +x sslip.io-dns-server-linux-amd64
./sslip.io-dns-server-linux-amd64

To make the setup fully functional, delegate a subdomain of your domain, such as ip.example.com, to the server running sslip.io-dns-server using NS records in DNS. Then 1-2-3-4.ip.example.com will resolve to 1.2.3.4, and Let's Encrypt's rate limits will be counted against your domain rather than the shared sslip.io domain.


Limitations

  • Let's Encrypt rate limits. As we have seen, sslip.io can hit rate limits because of its popularity. nip.io is a fallback, but it is not immune either. For large deployments, run your own instance as described above or use your own domain.
  • Dependence on external DNS. If the service's DNS servers become unavailable, new clients will be unable to resolve the domain. Existing TLS connections will continue working. Running your own instance removes this dependency.
  • A new IP means a new domain. If the server is assigned a different IP address, the old domain will stop working, and you will need to issue a certificate for the new one.
  • Not for production. For production services with users, a conventional domain is preferable: it is memorable, is not tied to a specific IP address, and looks professional.

Original screenshots and recordings may contain Russian text.