# Your public IP from the command line or in code | host.sh

> 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

| URL | Answer |
|---|---|
| `https://host.sh/ip` | Your address as plain text, for terminals (the [curl page](https://www.host.sh/curl/index.md) 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](https://www.host.sh/docs/lookup/index.md#your-ip-only-v2-ip). 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](https://www.host.sh/guides/ipv4-vs-ipv6/index.md) explains why.

## In a shell

```sh
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:

```text
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
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))
```

```js
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](https://www.host.sh/docs/rate-limits/index.md)). 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](https://www.host.sh/guides/public-vs-private-ip/index.md)).

## 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](https://www.host.sh/curl/index.md) and the [API docs](https://www.host.sh/docs/index.md).

## 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.
