whois-api-server on Ubuntu 24.04 on Azure User Guide
Overview
whois-api-server (the open source KincaidYang/whois) is a self-hosted WHOIS and RDAP query API. It looks up domains, IPv4 and IPv6 addresses, CIDR prefixes and AS numbers against the live WHOIS and RDAP registries on demand, parses the reply into clean, structured RDAP-vocabulary JSON, and caches it. Because a WHOIS proxy answers from the registries themselves, the image queries the public WHOIS (TCP 43) and RDAP (HTTPS 443) servers over the outbound network — there is no dataset to ship or keep up to date.
The server is a single self-contained native Go binary. It runs behind nginx as a reverse proxy on port 80. The API listens on 127.0.0.1:8043 and, because the upstream binary binds every interface, the image adds an nftables rule that fences the app port to the loopback interface, so the only public surface is nginx.
whois-api-server is a public-by-design lookup service: the WHOIS/RDAP lookup and the web UI need no login because there is no default credential to leak, so none ships in the image or is generated at first boot. On the first boot of every deployed VM a one-shot service resolves the VM public address, writes an instance info file to /root/whois-api-server-info.txt, records the bootstrap-ready marker, and starts the API and the front door. The API unit is gated on that marker, so it never serves before first boot has completed.
To make the image useful the moment it boots, cloudimg bundles a fully self-contained lookup web UI, served locally by nginx, that runs a query in the browser and renders the parsed registry data with no external CDN.

What is included:
- whois-api-server 1.2.0 (single native Go binary) at
/opt/whois-api-server/whois-server - nginx reverse proxy on
:80in front of the API on loopback:8043, with a strict path allowlist, version disclosure suppressed and per-IP rate limiting on the lookup path - A self-contained cloudimg lookup web UI served at
/(vanilla HTML/JS, no external CDN) that queries the API and renders the result - In-memory caching (no Redis dependency by default), so repeated queries do not re-hit upstream registries
whois-api-server.serviceandnginx.serviceas systemd units, enabled and active, plus awhois-api-server-firstboot.serviceone-shot that runs once per VM, and annftables.serviceloopback fence for the app port- The API runs as a dedicated non-root
whoisservice account with no shell and no sudo - A fully patched Ubuntu 24.04 LTS security baseline at capture time, with unattended security updates enabled
- 24/7 cloudimg support
Key facts: platform Ubuntu 24.04 LTS on Azure, default SSH user azureuser, binary /opt/whois-api-server/whois-server, config /etc/whois/config.yaml, web UI and lookup API on port 80, API loopback on 8043.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point: the service is a small Go binary with an in-memory cache, so it needs little memory. NSG inbound: allow 22/tcp from your management network and 80/tcp (plus 443/tcp once you place your own TLS terminator in front) from the networks that will consume the API. The VM needs outbound access to the internet so it can reach the upstream WHOIS (43/tcp) and RDAP (443/tcp) servers.
Step 1 — Deploy from the Azure Marketplace
Find the cloudimg whois-api-server on Ubuntu 24.04 LTS offer in the Azure Marketplace and select Get It Now, then Create. Choose your subscription, resource group and region, pick Standard_B2s, and provide your SSH public key for the azureuser account. On the Networking tab attach the VM to your VNet/subnet and a network security group that allows inbound 22/tcp from your management network and 80/tcp from your API consumers. Review and create.
Step 2 — Deploy from the Azure CLI
You can deploy the same image from the CLI. Create the network, then the VM from the cloudimg image plan (replace the placeholder names for your environment):
az group create --name whois-rg --location uksouth
az network vnet create --resource-group whois-rg --name whois-vnet \
--address-prefix 10.20.0.0/16 --subnet-name whois-subnet --subnet-prefix 10.20.1.0/24
az network nsg create --resource-group whois-rg --name whois-nsg
az network nsg rule create --resource-group whois-rg --nsg-name whois-nsg \
--name allow-ssh --priority 1000 --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create --resource-group whois-rg --nsg-name whois-nsg \
--name allow-http --priority 1010 --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create --resource-group whois-rg --name whois-vm \
--image <cloudimg-whois-api-server-image-urn> \
--size Standard_B2s --admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--vnet-name whois-vnet --subnet whois-subnet --nsg whois-nsg --public-ip-sku Standard
Step 3 — Connect to your VM
ssh azureuser@<vm-ip>
The first-boot service runs automatically. Its message-of-the-day banner and /root/whois-api-server-info.txt show the URLs for this VM.
Step 4 — Verify the stack
Confirm the two services are active, that nginx is public on :80 while the API is on :8043, and that the nftables rule fences the app port to loopback:
systemctl is-active whois-api-server nginx whois-api-server-firstboot
ss -tln | grep -E ':80 |:8043 '
sudo nft list ruleset | grep -A4 'table inet whois_filter'
Expected output:
active
active
active
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 *:8043 *:*
LISTEN 0 511 [::]:80 [::]:*
table inet whois_filter {
chain input {
type filter hook input priority filter; policy accept;
iif "lo" accept
ct state established,related accept
tcp dport 8043 drop
The API binds :8043 on all interfaces, but the tcp dport 8043 drop rule (for anything not arriving on lo) means it is reachable only through nginx or from on-box localhost.

The health probe confirms the in-memory cache backend:
curl -s http://127.0.0.1/health
{"status":"ok","timestamp":"2026-08-08T11:18:13Z","uptime":"3m35s","checks":{"cache":{"status":"ok","message":"memory"}}}
Step 5 — Open the lookup web UI
Browse to http://<vm-ip>/. The cloudimg lookup page lets you query any domain, IP address, CIDR prefix or AS number and renders the parsed registry data. You can also link straight to a result with the query in the URL fragment, for example http://<vm-ip>/#8.8.8.8.

Step 6 — Query a domain over the API
Every lookup is also a plain HTTP GET. The /api/ path is the rate-limited nginx front door to the query engine; ask it for a domain and you get structured RDAP JSON with the registrar, the registration and expiry dates, the domain status codes and the nameservers:
curl -s http://127.0.0.1/api/google.com | jq '{ldhName,registrar,registrarIanaId,registrationDate,expirationDate,status,nameservers}'
{
"ldhName": "google.com",
"registrar": "MarkMonitor Inc.",
"registrarIanaId": "292",
"registrationDate": "1997-09-15T04:00:00Z",
"expirationDate": "2028-09-14T04:00:00Z",
"status": [
"client delete prohibited",
"client transfer prohibited",
"client update prohibited",
"server delete prohibited",
"server transfer prohibited",
"server update prohibited"
],
"nameservers": [
"ns1.google.com",
"ns2.google.com",
"ns3.google.com",
"ns4.google.com"
]
}
This is genuine registry data fetched live and parsed — not a canned response. Add ?raw=1 to a domain query to get the unparsed WHOIS text instead.

Step 7 — IP and ASN lookups, and the front-door allowlist
The same endpoint auto-detects IPv4/IPv6 addresses, CIDR prefixes and AS numbers, and the typed paths /api/ip/... and /api/autnum/... are available too:
curl -s http://127.0.0.1/api/8.8.8.8 | jq '{objectClassName,handle,name,startAddress,endAddress,cidr}'
curl -s http://127.0.0.1/api/AS15169 | jq '{objectClassName,handle,name}'
{
"objectClassName": "ip network",
"handle": "NET-8-8-8-0-2",
"name": "GOGL",
"startAddress": "8.8.8.0",
"endAddress": "8.8.8.255",
"cidr": "8.8.8.0/24"
}
{
"objectClassName": "autnum",
"handle": "AS15169",
"name": "GOOGLE"
}
The nginx front door only publishes the UI, /api/ and /health. The operational and AI-integration endpoints the binary also exposes (/metrics, /info, /mcp, /openapi.json) and the bulk /batch endpoint are not reachable from the internet — they return 404 at the front door and are available only on-box via 127.0.0.1:8043:
for p in /metrics /info /mcp /openapi.json /batch; do
echo "$p -> $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1$p)"
done
/metrics -> 404
/info -> 404
/mcp -> 404
/openapi.json -> 404
/batch -> 404

The same lookup works for any domain — here a second example rendered in the web UI:

Step 8 — Per-VM instance info and hardening
Every VM writes its own info file at first boot with the URLs for this instance and a summary of the hardening in the image:
sudo cat /root/whois-api-server-info.txt

Why this is safe to expose as a public lookup: the core lookup is read-only and carries no credential, so there is nothing to compromise. The abuse surface is closed by design — the app port is fenced to loopback, /batch is disabled, the ?refresh cache-bypass is refused while the instance is open (so nobody can force the proxy to hammer registries), the upstream concurrency limit caps how many queries the proxy makes at once, and nginx rate-limits the /api/ path per client IP so it cannot be used as an unbounded open relay. In-memory caching means repeated queries are answered without touching upstream at all.
Step 9 — Require an API key (optional lock-down)
If you want the lookup itself restricted to authenticated clients, set one or more API keys. Every endpoint except /health and /ready then requires an Authorization: Bearer <key> or X-API-Key: <key> header, and clients with a key may also use ?refresh and per-key rate limits. Drop in a systemd override and restart:
sudo mkdir -p /etc/systemd/system/whois-api-server.service.d
sudo tee /etc/systemd/system/whois-api-server.service.d/apikey.conf >/dev/null <<'EOF'
[Service]
Environment=WHOIS_AUTH_KEYS=<your-token>
EOF
sudo systemctl daemon-reload && sudo systemctl restart whois-api-server
You can also edit auth.keys (and enable batch) directly in /etc/whois/config.yaml. See the upstream configuration reference for per-key names and rate limits.
Server Components
- whois-api-server 1.2.0 — the WHOIS/RDAP query API (single native Go binary),
/opt/whois-api-server/whois-server, run as thewhoisservice account. - nginx — reverse proxy and front door on
:80, serving the lookup UI and proxying/api/and/healthto the loopback API, withserver_tokens offand a per-IPlimit_reqrate limit. - nftables — a loopback fence (
/etc/nftables.conf) dropping non-loopback inbound totcp/8043. - cloudimg lookup web UI — a self-contained HTML/JS page served from
/var/lib/whois-api-server/viewer.
Filesystem Layout
/opt/whois-api-server/whois-server— the binary/opt/whois-api-server/LICENSE,/opt/whois-api-server/COMPONENTS.txt— upstream MIT licence and component notice/etc/whois/config.yaml— the hardened configuration (working directory of the unit)/var/lib/whois-api-server/viewer/index.html— the lookup web UI/etc/nftables.conf— the app-port loopback fence/root/whois-api-server-info.txt— the per-VM instance info written at first boot
Managing the service
systemctl status whois-api-server --no-pager
sudo journalctl -u whois-api-server -n 50 --no-pager
Restart the API after you change its configuration with sudo systemctl restart whois-api-server.
Security
The API runs as a non-root service account. The app port is fenced to loopback with nftables and nginx is the only public surface, with a strict path allowlist so the operational, MCP and batch endpoints are never exposed to the internet. The lookup is public by design and ships no credential; set WHOIS_AUTH_KEYS (Step 9) to require an API key on every endpoint. nginx rate-limits /api/ per client IP and the binary caps concurrent upstream queries, so the proxy cannot be abused as an unbounded open relay. Keep the OS patched — unattended security updates are enabled in the image.
Troubleshooting
- A lookup times out or returns an upstream error — the VM needs outbound access to the WHOIS (
43/tcp) and RDAP (443/tcp) servers. Check the NSG and any egress firewall. /api/...returns 502 — the API is not running; checksystemctl status whois-api-serverandsudo journalctl -u whois-api-server. The unit is gated on the first-boot marker/var/lib/cloudimg/whois-api-server-firstboot.done./api/...returns 401 after you set a key — the instance now requires an API key; sendAuthorization: Bearer <key>(or unsetWHOIS_AUTH_KEYSto reopen it)./api/...returns 429 — the per-IP rate limit engaged; tunelimit_reqin/etc/nginx/conf.d/cloudimg-whois-api-server-hardening.conffor your traffic.
Support
cloudimg images include 24/7 support. Contact support@cloudimg.co.uk with the offer name and your VM details.