# PushProx: scraping Prometheus targets behind NAT

> PushProx is a client and a proxy that let Prometheus keep its pull model while scraping machines it cannot reach directly, with the client polling the proxy for work and the proxy routing each scrape by fully qualified domain name. The README states there is no authentication, and that running the client exposes every network service on that machine to anyone who can reach the proxy.

**prometheus-community/PushProx** — Proxy to allow Prometheus to scrape through NAT etc.

- Repository: https://github.com/prometheus-community/PushProx
- Stars: 810 · Forks: 154
- Language: Go
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/prometheus-community-pushprox

## The problem PushProx solves

PushProx is a client and proxy that allows traversing NAT and other similar network topologies by Prometheus, while still following the pull model.

That last clause is the design decision everything else follows from. The obvious alternative to scraping a machine behind NAT is to have that machine push its metrics to a collector, and Prometheus has a push path for that, the remote write receiver. PushProx exists for the case where an organisation wants to keep the pull model, because the pull model is what the rest of the monitoring stack is built around, and because switching to push changes who initiates the connection, which changes firewall rules, which changes who has to be involved in a change.

So instead of changing the model, PushProx inserts a relay. The proxy sits somewhere both Prometheus and the clients can reach, and the clients are the ones that make the outbound connection.

The README is candid about the maturity. It says that while this is reasonably robust in practice, this is a work in progress. The repository is under the prometheus-community organisation, the licence is Apache-2.0, and the release history shows v0.2.0 from 2024-03-28 and v0.1.0 from 2020-05-26, with the last push on 2026-10-15.

The security warning, which is in its own section, is what makes the deployment decision. There is no authentication or authorisation included, a reverse proxy can be put in front though to add these. And running the client allows those with access to the proxy or the client to access all network services on the machine hosting the client.

## Building and running the two binaries

The build is a make target from a clone.

```bash
git clone https://github.com/prometheus-community/pushprox.git
cd pushprox
make build
```

That produces two executables, the proxy and the client, and they run as separate processes on separate machines.

The proxy runs where both Prometheus and the clients can reach it.

```bash
./pushprox-proxy
```

The client runs on every target machine, pointed at the proxy.

```bash
./pushprox-client --proxy-url=http://proxy:8080/
```

The default port in the example is 8080, and the proxy URL is the only thing the client needs to know. The client has no configuration for the targets, because it does not scrape anything itself; it is a relay for the request that Prometheus wants to make.

The Makefile is the standard Prometheus one. It includes Makefile.common, which is the shared build machinery used across Prometheus projects and provides the usual build, test, lint and cross-compilation targets. The DOCKER_REPO is set to prometheuscommunity and the image name is pushprox, with a .promu.yml for the release tooling and a Dockerfile that copies the cross-built binaries into a busybox base image.

## The Prometheus configuration and the TLS workaround

Prometheus treats the proxy as a proxy_url in the scrape configuration.

```yaml
scrape_configs:
- job_name: node
  proxy_url: http://proxy:8080/
  static_configs:
    - targets: ['client:9100']  # Presuming the FQDN of the client is "client".
```

The important detail is in the target. It is the client:9100 form, the node exporter's port on the client, and the client is identified by its fully qualified domain name, which is the key the whole scheme turns on.

The TLS case needs a workaround, and the README explains why. If the target must be scraped over SSL/TLS, you add a params block with _scheme set to https.

```yaml
  params:
    _scheme: [https]
```

rather than the usual scheme: https. The stated reason is that only the default scheme: http works with the proxy, so the workaround is required. The mechanism is that scheme is consumed by the proxy layer itself, since the proxy is what Prometheus is really talking to, and the target's own scheme has to be smuggled through as a parameter instead.

That is a small piece of friction, and it is the kind of thing that trips up a first deployment: a target configured with scheme: https will fail against the proxy, and the error will look like a protocol problem rather than a configuration one.

## The polling sequence and how a scrape is routed

The How It Works section describes the mechanism step by step, and a sequence diagram is included in the docs directory.

Clients perform scrapes in a network environment that is not directly accessible by Prometheus, and the proxy is accessible by both the clients and Prometheus. Each client is identified by its fqdn.

For a target named fqdn-x, the sequence runs like this. First, a client polls the proxy for scrape requests and includes its fqdn in the poll, and the proxy does not respond yet. Next, Prometheus tries to scrape the target with hostname fqdn-x via the proxy. Using the fqdn received in the first poll, the proxy now routes the scrape to the correct client, and the scrape request is in the response body of the poll. The scrape request is executed by the client, the response containing metrics is posted to the proxy, and on its turn the proxy returns this to Prometheus as a response to the initial scrape.

Two design points are worth pulling out of that. The proxy does not answer the poll immediately, it holds the poll open until there is work, which is why the client is always waiting rather than polling in a loop. And the routing key is the fqdn, which means the client's name has to resolve consistently on the Prometheus side, because Prometheus uses that name to address the target and the proxy uses it to find the client.

The README also notes that PushProx passes all HTTP headers transparently, and that features like compression and accept encoding are up to the scraping Prometheus server. So the wire format between Prometheus and the proxy is not limited to a metrics body, and the proxy is not doing anything clever with the content.

## Service discovery through the clients endpoint

There is one piece of dynamic behaviour, and it is the endpoint that makes PushProx usable with a changing fleet.

The /clients endpoint returns a list of all registered clients, in the format used by file_sd_configs. The README suggests using wget in a cronjob to put it somewhere file_sd_configs can read and then relabel as needed.

That is a small, honest design. Rather than implementing a service discovery protocol, PushProx exposes a list in a format Prometheus already understands, and leaves the plumbing to a cron job and a file. The consequence is that the discovery interval is bounded by how often the cron job runs, so a machine that comes online is visible to Prometheus only after the next run, which is a real difference from native service discovery with its own refresh.

The relabeling step matters too, because the clients endpoint lists clients, and Prometheus needs a target list. A client's fqdn has to be turned into the target address, which usually means matching against the port the exporter listens on, and the README's mention of relabelling as needed is pointing at exactly that gap.

So the operational picture is: clients register by polling, the proxy knows who is out there, and you either write the targets statically or script the discovery through this endpoint.

## The security model, stated plainly

This is the section to read before anything else, and it is unusually blunt about the consequences.

There is no authentication or authorisation included, a reverse proxy can be put in front though to add these.

Running the client allows those with access to the proxy or the client to access all network services on the machine hosting the client.

The second sentence is the one that determines whether this is deployable. The client is not a metrics-only agent. It is a component that can be instructed to fetch a URL, and if the proxy will accept a request from anyone who can reach it, then anyone who can reach the proxy can use the client as a way to reach internal services on that machine. On a target that exposes only a node exporter, the blast radius is small. On a target with a database port, an admin interface or an internal API listening on it, the proxy becomes a pivot into that network.

So the reverse proxy is not a nice-to-have. It is the thing that turns a NAT traversal utility into something that can be exposed, and the authentication it adds is the boundary that keeps the client from being an open relay.

The repository carries a SECURITY.md, and the licence is Apache-2.0. The design is defensible for a monitoring tool, since the traffic is metrics and the participants are trusted, but it is only defensible when the participants really are trusted, and the README says as much.

## Where PushProx is the wrong tool

The first limitation is that it solves NAT traversal and nothing else. If the target is reachable from Prometheus, adding a proxy in the path adds a hop, a failure point and a place for a scrape to stall, for no benefit. PushProx is for the specific case where the network topology forbids a direct connection.

The second is the absence of authentication, already covered. It is a hard limit on where it can run: not on a network with untrusted participants, and not in front of a client on a host with valuable internal services unless the reverse proxy is doing its job.

The third is the pull model constraint itself. If your organisation has decided to move to push, or if the monitoring stack is a push-based system, PushProx is the wrong shape, because it exists specifically to preserve the pull model.

The fourth is the discovery latency, since the /clients endpoint is polled by a cron job on your schedule, so the time between a machine coming online and Prometheus knowing about it is your cron interval plus a scrape cycle.

The fifth is the project state. The README calls it a work in progress, the newest tagged release is v0.2.0 from 2024, and the go.mod requires Go 1.26.0. A component sitting between Prometheus and every target is not where you want an unversioned work in progress, and the CHANGELOG.md is where the actual rate of change is recorded.

## PushProx compared with a push gateway, a VPN and the Prometheus Agent mode

The comparison with a push gateway is the direct one, and the difference in approach is who initiates the connection. With a push gateway, the target machine opens a connection to the collector and posts metrics, and the collector stores them. With PushProx, the target machine opens a connection to the proxy and waits, and Prometheus still initiates the scrape, so the data path is unchanged and only the network topology is accommodated.

That is the whole argument for PushProx over a push gateway. If your requirement is to get metrics out of a network, a push gateway does it more simply and with fewer moving parts. PushProx is for the requirement that Prometheus must remain the one pulling.

The comparison with a VPN is the other obvious one. A VPN or a WireGuard tunnel solves the same topology problem at the network layer, and it does so with encryption and authentication built in. The difference is that a tunnel requires every machine to join the network, which is a change to the network estate, while PushProx requires one process per target and one reachable proxy.

The fourth comparison is with the Prometheus Agent mode, which runs a daemon on each target and pushes to a remote write receiver, and with the newer collectors that expose an OpenMetrics endpoint over the network. The difference is again the direction of the connection, and the fact that an agent mode daemon is a monitoring daemon you have to operate and secure on every host, whereas the PushProx client is a much smaller thing.

There is also the case where the target is behind a corporate proxy or a VPN that only the target can reach, which is the scenario PushProx is built for and where a reverse proxy in front of the Prometheus side would not help at all.

## Licence, packaging and what to check before deploying

The licence field on the repository records Apache-2.0, and the Makefile carries the Apache 2.0 header with the Prometheus Authors copyright, which is the standard form for a project in the Prometheus ecosystem.

The packaging follows the Prometheus conventions closely. The Makefile includes Makefile.common, the shared build file used across the ecosystem, which is what gives the project the usual make build, make test and cross-compilation targets for free. The .promu.yml configures the release tooling, the Dockerfile copies the cross-built binaries into a quay.io/prometheus/busybox base image, and it defaults to the proxy as the entrypoint while noting that this can be overridden with the docker entrypoint flag or the command field in the Kubernetes container v1 API. The image runs as the nobody user.

The go.mod is small and tells you the shape of the code. It requires the kingpin command line parser, the Prometheus client library and common packages, the uuid package, the go-fqdn package, which is what identifies each client, and the backoff library, which is presumably what makes the client's polling resilient.

Four things to check before you deploy this. That a reverse proxy with authentication sits in front of the proxy process, because without it the client is an open relay into the target host. That the fqdn of each client resolves consistently, since it is the routing key on both sides. That the TLS workaround is applied where a target needs https, using the params _scheme form rather than the scheme key. And that the discovery cron job is scheduled at a rate that matches how quickly you need to notice a machine that has gone away.

## Conclusion

PushProx suits an operator with machines behind NAT that Prometheus cannot reach, and who wants to keep the pull model rather than switch to a push model with a remote write receiver. It is the wrong choice for a network where the target is already reachable, and definitely the wrong choice for a hostile or shared network, since the README says there is no authentication or authorisation and that running the client lets anyone with access to the proxy or the client reach all network services on that machine. Before deploying, put an authenticating reverse proxy in front of the proxy process, and if you need TLS on the target, remember that the scrape configuration needs the _scheme param workaround rather than the usual scheme key.

## FAQ

### What does PushProx do for Prometheus?

It lets Prometheus scrape targets that are not directly reachable, such as machines behind NAT, while still following the pull model. A client on each target polls a shared proxy for work, and the proxy routes each scrape to the right client by fully qualified domain name.

### How do I configure Prometheus to scrape through PushProx?

Set proxy_url on the scrape config, for example http://proxy:8080/, and list the targets as the client's fqdn with the exporter's port, such as client:9100. The proxy has to be somewhere both Prometheus and the clients can reach.

### How do I scrape a TLS target through the PushProx proxy?

Add a params block with _scheme set to https rather than using the usual scheme key. The README says only the default scheme: http works with the proxy, so this workaround is required.

### Does PushProx have authentication?

No. The README states there is no authentication or authorisation included and that a reverse proxy can be put in front to add these. It also warns that running the client allows those with access to the proxy or the client to access all network services on the machine hosting the client.

### How does PushProx discover clients?

The /clients endpoint returns a list of all registered clients in the format used by file_sd_configs. The README suggests using wget in a cronjob to write that list somewhere file_sd_configs can read and then relabel as needed.

### How do I build and run PushProx?

Clone the repository and run make build, which produces pushprox-proxy and pushprox-client. Run the proxy where Prometheus and the clients can both reach it, and on each target run the client with --proxy-url pointing at the proxy, for example ./pushprox-client --proxy-url=http://proxy:8080/.

## Sources

- [Issues](https://github.com/prometheus-community/PushProx/issues)
- [License: Apache-2.0](https://github.com/prometheus-community/PushProx/blob/master/LICENSE)
- [prometheus-community/PushProx on GitHub](https://github.com/prometheus-community/PushProx)
- [README](https://github.com/prometheus-community/PushProx/blob/master/README.md)
- [Releases](https://github.com/prometheus-community/PushProx/releases)

---

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