Your public IP from the command line or in code

Get your public IP address in bash, fish, PowerShell, Python, Node, Go and PHP: IPv4 or IPv6, with timeouts and a check that the answer is an IP.

Your computer knows its own network address, but not the public address the internet sees: that one belongs to your router or your provider, and only a server you talk to can tell you. This guide shows how to ask one from a shell or a program, how to get the IPv4 or the IPv6 address on purpose, and how to make the answer safe to use in a script. All the examples call host.sh and need no key, and the shell, Python, Node, Go and PHP ones run in host.sh's CI on every change.

The endpoints

URLAnswer
https://host.sh/ipYour address as plain text, for terminals (the curl page has more fields)
https://api.host.sh/v2/ip/Your address as plain text; ?format=json gives {"ip": "…"}
https://ipv4.host.sh/v2/ip/Your IPv4 address, even on a connection that prefers IPv6

The API endpoints are described in Lookup. On a dual-stack connection, host.sh and api.host.sh usually answer with your IPv6 address, because systems prefer IPv6 by default when it works; the IPv4 vs IPv6 guide explains why.

In a shell

ip=$(curl -sS --fail-with-body --max-time 5 https://host.sh/ip) || exit 1
case "$ip" in ''|*[!0-9a-fA-F.:]*) echo "not an IP address: $ip" >&2; exit 1;; esac
echo "My public IP is $ip"

The same in other shells and tools:

set ip (curl -fsS --max-time 5 https://host.sh/ip)            # fish
$ip = (Invoke-RestMethod -TimeoutSec 5 https://host.sh/ip).Trim()  # PowerShell
wget -qO- --timeout=5 https://host.sh/ip                       # wget
http --body https://host.sh/ip                                 # HTTPie
curl -4 -fsS https://host.sh/ip                                # only IPv4
curl -6 -fsS https://host.sh/ip                                # only IPv6, fails without it

Two curl flags make the difference in a script: --fail-with-body turns an HTTP error into a failing exit status (curl 7.76 and later; -f does the same on older versions, without printing the error's body), and --max-time keeps a broken network from hanging the script. The || exit 1 stops the script when curl fails, instead of carrying on with an empty variable, and the case line is a quick sanity check: it stops the script when the answer has anything but the digits, letters, dots and colons of an address, such as an error page. It can't tell 999.999.999.999 from a real address; for a full check, parse the answer in one of the languages below.

In code

Each example sets a timeout and parses the answer as an IP address before using it:

Python (requests)
import ipaddress
import urllib.request

# your public address as websites see it; ipv4.host.sh for the IPv4 one
with urllib.request.urlopen("https://api.host.sh/v2/ip/", timeout=5) as r:
    ip = ipaddress.ip_address(r.read().decode().strip())  # ValueError if the answer isn't an address
print(ip, "IPv" + str(ip.version))
JavaScript (fetch)
import { isIP } from 'node:net';

// Node 18 or later: fetch is built in
const res = await fetch('https://api.host.sh/v2/ip/?format=json', { signal: AbortSignal.timeout(5000) });
if (!res.ok) throw new Error('HTTP ' + res.status);
const { ip } = await res.json();
if (!isIP(ip)) throw new Error('not an IP address: ' + ip);
console.log(ip);
Go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/netip"
	"strings"
	"time"
)

func main() {
	client := &http.Client{Timeout: 5 * time.Second}
	res, err := client.Get("https://api.host.sh/v2/ip/")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		panic(res.Status)
	}
	body, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	ip, err := netip.ParseAddr(strings.TrimSpace(string(body)))
	if err != nil {
		panic(err)
	}
	fmt.Println(ip)
}
PHP
<?php
// your public IPv4 address; api.host.sh answers with whichever family the connection used
$ctx = stream_context_create(['http' => ['timeout' => 5]]);
$body = file_get_contents('https://ipv4.host.sh/v2/ip/', false, $ctx);  // false on a network or HTTP error
$ip = $body === false ? '' : trim($body);
if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
    fwrite(STDERR, "no IP address in the answer\n");
    exit(1);
}
echo $ip, "\n";

The Python, Node and Go examples ask api.host.sh, so they usually print your IPv6 address when your connection has one; the PHP one asks ipv4.host.sh for the IPv4 address. ipv4.host.sh always answers with IPv4, but api.host.sh answers with whichever family the connection used, which is IPv4 when IPv6 isn't working. If you need an IPv6 address, check the parsed address's version (or use curl -6, which fails without IPv6).

Make it safe to use

  • Validate the answer. Parse it as an address, as the code examples do (the shell case line is only a sanity check), before putting it in a firewall rule, a DNS record or a log. A proxy error page or an empty response should stop your script, not become your "IP".
  • Set a timeout and handle failure: a script that runs at boot may start before the network is up.
  • Ask once, not in a loop. curl host.sh answers from a copy kept for an hour for your address, and tokenless lookups through the API are limited to 10 a minute per IP address (rate limits). A dynamic-DNS script that checks every few minutes is fine.
  • Pick the family you need. A DNS A record or an IPv4 allow-list needs your IPv4 address: ask ipv4.host.sh or use curl -4. An AAAA record needs curl -6, and note that many systems use temporary IPv6 addresses that change daily (public vs private IP).

More than the address

The same request can tell you more: curl host.sh prints your address with its location, network and time zone as JSON, and curl host.sh/country_code prints a single field.

In code, https://api.host.sh/v2/lookup/ returns the full answer for your own address without a token; looking up other addresses needs one. See the curl page and the API docs.

FAQ

How do I get my public IP address from the command line?

Run curl https://host.sh/ip. It prints your public IP address as plain text, with no key or sign-up. Add -4 or -6 to get your IPv4 or IPv6 address.

Why does my script get an IPv6 address when I wanted IPv4?

Your connection has both, and systems prefer IPv6 by default. Ask https://ipv4.host.sh/v2/ip/, which can only be reached over IPv4, or use curl -4.

How do I get my public IP in Python?

Read https://api.host.sh/v2/ip/ with urllib.request and a timeout, strip the newline, and check it with ipaddress.ip_address. The example above does all three with the standard library only.

Is there a limit on how often I can ask?

Tokenless lookups through the API are limited to 10 a minute per IP address, and curl host.sh keeps your answer for an hour. Asking every few minutes, as a dynamic-DNS script does, is fine.

This page as Markdown: /guides/public-ip-command-line/index.md