# echoip: the small Go service behind ifconfig.co

> A single Go binary that answers IP, country, city and ASN lookups over plain text or JSON, plus a scratch-based Docker image. Worth reading because it shows how little a useful lookup service actually needs.

**mpolden/echoip** — A simple IP address lookup service.

- Repository: https://github.com/mpolden/echoip
- Website: https://ifconfig.co
- Stars: 4,356 · Forks: 567
- Language: Go
- License: BSD-3-Clause
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/mpolden-echoip

## A plain text address reply that five clients agree on

The README opens with the smallest thing a service of this kind can do: print the address of the machine that asked, and nothing else. Every command-line HTTP client gets the same reply.

```bash
$ curl ifconfig.co
127.0.0.1
```

The list also covers `http`, `wget`, `fetch`, and `bat -print=b ifconfig.co/ip`. Supporting `bat` matters more than it first appears, because a shell prompt that pipes `curl` through a pager is the main way people hit these services interactively. A response with the wrong content type or an HTML body breaks that pipeline, so the service returns a bare address and a newline.

The domain name is part of the product. The README lists an easy to remember domain name as the first feature, which is a reasonable claim for a service whose entire interface is one URL: `ifconfig.co` reads like a shell command, so a human can recall it without a bookmark.

## Country, city and ASN come from three downloaded MaxMind files

The README states it plainly: `echoip` uses the MaxMind GeoIP databases to show additional information about IP addresses, such as registered country/city and ASN details.

```bash
$ curl ifconfig.co/country
Elbonia

$ curl ifconfig.co/country-iso
EB
```

Those databases are not committed to the repository. The Makefile lists `GeoLite2-City`, `GeoLite2-Country` and `GeoLite2-ASN` as targets under `geoip-download`, and both credentials are checked before anything is fetched, so a missing key produces a make error naming the two variables rather than a silently empty database.

```bash
GEOIP_LICENSE_KEY=<key> MAXMIND_ACCOUNT_ID=<account-id> make geoip-download
```

After the download the Makefile renames the extracted files to `data/city.mmdb`, `data/country.mmdb` and `data/asn.mmdb`, which lines up with the flag names in the usage output: `-a` for the ASN database, `-c` for city, `-f` for country. The JSON endpoint reports all of it in one object, including `ip_decimal` for the address as an integer.

Dependency weight is low. `go.mod` names a single direct requirement, `github.com/oschwald/geoip2-golang v1.13.0`, and declares Go 1.24. The trade-off to keep in mind is data quality rather than code quality: the free GeoLite tier is what these flags point at by default, so country and city answers are only as current as the files you download.

## Port testing is opt-in and refuses to look up other addresses

The `/port` endpoint turns the service into a reachability check for one TCP port on the caller's own address.

```bash
$ curl ifconfig.co/port/80
{
  "ip": "127.0.0.1",
  "port": 80,
  "reachable": false,
  "status": "refused"
}
```

Two details are worth pausing on. The README says all endpoints except `/port` accept a custom address through the `?ip=` query parameter, and this one is deliberately excluded, so a public instance cannot be used to probe arbitrary hosts. It is also disabled unless you pass `-p`, which the usage block describes as `Enable port lookup`.

The response shape is flat on purpose: address, port, a boolean, and a status word such as `refused`. That is enough for a shell script to branch on without pulling in a JSON parser, and it keeps the handler small enough to read in one sitting.

## The Makefile holds the project's own quality bar

Installing from source takes one line, and the README is explicit about the prerequisite: compiling requires the Golang compiler.

```bash
go install github.com/mpolden/echoip/...@latest
```

The more interesting file is the Makefile, because its default target encodes what the maintainer considers done.

```bash
go test ./...
go vet ./...
gofmt -w .
```

The `all` target is `checkfmt vet test install`, in that order. `checkfmt` does not rewrite anything: it shells out to `gofmt -l .` and fails with a message telling you to try `make fmt` to fix it automatically. So a plain `make` refuses to install a binary from code that is not gofmt-clean, and the repository tree carries a `.github/` directory whose workflow badge sits at the top of the README.

## A scratch image that ships no databases

A published container image is available on Docker Hub as `mpolden/echoip`.

```bash
docker pull mpolden/echoip
```

The Dockerfile is worth reading for what it leaves out. It is two stages: the build stage starts `FROM golang:1.24`, sets `CGO_ENABLED=0` with a comment saying the build must work without cgo because libc is unavailable in the runtime image, and runs `make`. The runtime stage is `FROM scratch`, exposes 8080, and copies exactly two things, the compiled binary and the `html` template directory.

No database files are copied, because nothing in the build creates them and `geoip-download` needs credentials the build does not have. The practical result is that the image answers the address and port endpoints out of the box, and the country, city and ASN endpoints need paths supplied at run time through `-c`, `-f` and `-a`. A scratch base also means there is no shell in the container if the entrypoint fails to start.

The Makefile closes the loop with `docker-build`, which tags `mpolden/echoip`, and `docker-test`, which runs the image detached, reads the mapped port back with `docker port`, curls it with a five second timeout, and stops the container.

## The trust decisions live in two flags and nowhere else

The usage block is the entire configuration surface, and two entries in it decide how much you trust the network in front of the service.

```bash
Usage of echoip:
  -H value
        Header to trust for remote IP, if present (e.g. X-Real-IP)
  -L    Disable custom IP lookup
```

`-H` exists for running behind a reverse proxy, where the socket address is the proxy and the real client address arrives in a header. Pass it only when nothing outside your proxy can set that header, otherwise any caller can claim any address and every downstream decision based on geolocation becomes fiction. `-L` is the counterweight on the other side: the README advertises `?ip=` on every endpoint except `/port`, and this flag turns that off so responses always describe the connection.

The remaining flags are cosmetic or operational: `-C` sets the response cache size and zero disables it, `-P` enables profiling handlers, `-r` turns on reverse hostname lookups, `-s` shows a sponsor logo, `-l` sets the listening address with a default of `:8080`, and `-t` points at the template directory. That the whole list fits on a screen is a fair summary of the project: this is a service with one data source and one trust boundary.

The repository publishes no GitHub releases, so there is no tagged artifact to pin and no changelog to read. The README's claim that the service is faster than ifconfig.me and adds IPv6 support is a comparison the project does not substantiate anywhere in the tree; IPv6 you can verify yourself by passing `-4` or `-6` to your client, as the README instructs.

## Conclusion

echoip is a good fit for a self-hosted address echo on an internal network, a debugging aid, or a starting point for a service that needs country and ASN context without standing up a database server of its own. It is the wrong choice if you need a public endpoint that will not become an open proxy for address lookups, and the wrong choice if you need SLA-backed geolocation accuracy, since the data is the free MaxMind GeoLite tier downloaded with your own credentials. The last push was on 2026-09-05 and the repository publishes no GitHub releases, so pin a container tag rather than tracking master. Start with `docker pull mpolden/echoip` and `-l :8080`, then add `-c`, `-f` and `-a` once you have run `make geoip-download`.

## FAQ

### How do I run my own echoip instance instead of using ifconfig.co?

Install it with `go install github.com/mpolden/echoip/...@latest`, which requires the Golang compiler, or pull the published image with `docker pull mpolden/echoip`. The binary listens on `:8080` by default, so pass `-l` to change the address.

### Does echoip let me look up an IP address other than my own?

Yes. The README states that all endpoints except `/port` can return information about a custom address supplied through the `?ip=` query parameter. The `-L` flag disables that custom lookup, and `/port` always probes the connection's own address so a public instance cannot be used to scan other hosts.

### Where does echoip get its geolocation data?

From the MaxMind GeoIP databases, which are not stored in the repository. The Makefile target `geoip-download` fetches GeoLite2-City, GeoLite2-Country and GeoLite2-ASN using `GEOIP_LICENSE_KEY` and `MAXMIND_ACCOUNT_ID`, then renames them to `city.mmdb`, `country.mmdb` and `asn.mmdb`, which you pass to the binary through `-c`, `-f` and `-a`.

### How does echoip compare with ifconfig.me?

The README claims it is faster and adds IPv6 support, but the repository contains no benchmark or measurements backing that comparison, so treat it as an unverified claim. IPv6 you can check directly by passing `-4` or `-6` to the client, as the README describes.

## Sources

- [Issues](https://github.com/mpolden/echoip/issues)
- [License: BSD-3-Clause](https://github.com/mpolden/echoip/blob/master/LICENSE)
- [mpolden/echoip on GitHub](https://github.com/mpolden/echoip)
- [Project website](https://ifconfig.co)
- [README](https://github.com/mpolden/echoip/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mpolden-echoip
