IP-address determination¶
The server records two IP addresses per agent:
- the transport IP that the server observes the chisel WebSocket connection coming from, and
- one or both of the agent's external IPv4 / IPv6 addresses as reported by the agent itself.
The transport IP is always known — it's the TCP source the kernel sees on the agent listener. The external IP is optional and is collected by the agent calling an HTTP IP-discovery API. This page covers when each value is right, why they can disagree, and how to configure the external-IP discovery.
Why both values exist¶
Three common deployments produce three different "the IP" answers:
- Direct internet exposure. The server is on the public internet, the agent dials it directly. The transport IP equals the agent's public IP. They always agree.
- Reverse proxy in front of the server. The proxy terminates
TLS; from the server's perspective every agent appears to come
from
127.0.0.1. The transport IP is useless for identifying the agent's network. - Agent behind a CGNAT or a corporate egress proxy. The server
sees the gateway IP, not the agent's. Even if the server reads
X-Forwarded-For, that header only carries the closest hop the proxy can see, not the original interface IP on the agent host.
The external-IP discovery exists for cases 2 and 3. The agent makes a small outbound HTTPS request to an IP-discovery API, the API echoes back the source IP it observed, and the agent reports that to the server. Because the agent's outbound HTTPS connection traverses the same NAT boundary as a real user request, the IP it gets back is the one inventory tools and ACLs usually want.
Enabling the discovery¶
Off by default, for privacy. The discovery makes one outbound HTTPS call per refresh interval to a server you do not control. Decide whether that is acceptable for your deployment before turning it on.
This call does not use the agent's proxy
The lookup builds its own HTTP connection and deliberately dials
IPv4 and IPv6 separately, so it does not go through proxy or
transports. On an agent routed over Tor or a VPN it would announce
the host's real address — which is the one thing that setup exists
to hide. With proxy set the agent ignores ip_api_url and logs a
warning; with transports set it refuses to start. See
Anonymous transport.
In
proxiport.conf
under [client]:
[client]
ip_api_url = "https://ifconfig.me/ip"
ip_refresh_min = 30
ip_refresh_min is the refresh interval in minutes. The default is
30; lower values produce more accurate state at the cost of more
outbound calls.
Supported IP-discovery APIs¶
The agent expects either of two response shapes:
- A plain-text body containing the IP and nothing else (
ifconfig.me,api.ipify.org). - A JSON object that includes an
ipfield:
{"ip": "203.0.113.42"}
Any extra fields are ignored. Examples in this shape include
https://api.my-ip.io/ip.json, https://api.myip.com, and
https://api.seeip.org/jsonip.
Verify the URL works over both IPv4 and IPv6 before deploying it. ProxiPort makes one IPv4 call and one IPv6 call per refresh — if the endpoint only responds to one family, the other column stays empty:
curl -4 -s https://ifconfig.me/ip; echo
curl -6 -s https://ifconfig.me/ip; echo
ProxiPort does not operate a first-party IP-discovery service.
Running your own IP API¶
A small PHP file on a webserver of yours is enough:
<?php
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ip' => $_SERVER['REMOTE_ADDR']]);
To gate access with HTTP basic auth, embed the credential in the URL:
[client]
ip_api_url = "https://probe:longpassword@ip.example.com/"
Running your own endpoint avoids two failure modes of the public services: throttling (a public service can rate-limit or block your agents in bulk) and disappearance (the service can go down or stop being free). It also keeps the discovery traffic inside your network boundary.
What the server does with the values¶
GET /api/v1/clients returns both kinds of address on each client
record:
address— the transport-level IP, including the port the server saw the connection from.ipv4andipv6— arrays containing any of the agent's own reported external addresses (when discovery is enabled), plus the addresses the agent enumerated from its local interfaces.
The SPA shows the discovered IPs on the per-client detail page. The
"Only my current IP address" preset on the tunnel-create form reads
the request's X-Forwarded-For (i.e. the operator's IP, not the
agent's), so it is unaffected by the agent's discovery setting.
Forwarded-for handling on the server¶
This takes two settings, one on each side. Forwarding the header
from the proxy is not enough on its own: the server ignores
X-Forwarded-For unless the socket peer is listed in
[api] trusted_proxies, and that list is empty by default.
Without it the audit log records the proxy's address — usually
127.0.0.1 — for every operator action, and GET /me/ip returns the
same, so the "Only my current IP address" tunnel ACL preset writes an
ACL for the loopback rather than for the operator. Neither failure is
reported: the header is simply not read.
1. Tell the server which peers may speak for someone else, in
proxiportd.conf:
[api]
## CIDRs or addresses of the reverse proxies in front of this server.
## Empty (the default) means X-Forwarded-For is never trusted.
trusted_proxies = ['127.0.0.1/32', '::1/128']
Use the address the proxy connects from, which for a proxy on the same host is the loopback the API is bound to. Keep this list as small as it can be: anything in it can claim to be any client.
2. Configure the proxy to send the header. For nginx:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
A Caddy reverse proxy adds X-Forwarded-For by default.
X-Forwarded-For from outside the trusted network must be stripped
at the edge — otherwise a client can spoof its source IP for audit
log entries. Most reverse proxies do this by default; double-check
yours.
With built-in TLS (no proxy in front) leave trusted_proxies unset.
The connection's remote address is the operator's address, and an
empty list is what stops a client from claiming otherwise.
To check it is working, make any state-changing API call through the
proxy and read the remote_ip on the resulting audit-log row: it
should be your address, not the proxy's.
Hardening checklist¶
- Decide whether the privacy tradeoff of outbound IP discovery is acceptable. If not, leave it off and rely on the transport IP.
- If you enable it, prefer a small endpoint you operate, ideally on HTTPS with basic auth, to keep the metadata trail inside your network.
- Always set the refresh interval to a value that won't get you throttled — 30 minutes is the default and is rarely the bottleneck.
- If you front the server with a reverse proxy, do both halves: set
[api] trusted_proxiesto the proxy's address and configure the proxy to forwardX-Forwarded-For. Either one alone leaves the audit log recording the proxy instead of the operator. With built-in TLS the connection remote address is used directly, no header configuration is needed, andtrusted_proxiesshould stay empty.
See also: client attributes for tagging agents
by location/role/datacenter, and
operator runbook — tunnels for the
single-IP ACL preset that reads X-Forwarded-For.