Safeguard
Software Supply Chain•

How do you run a private Go module proxy that acts as a gatekeeper?

A private Go proxy is a simple HTTP server. Point GOPROXY at it, set GONOSUMDB for private paths, and return 403 for modules you have not approved.

Safeguard Security Team
5 min read

A private Go module proxy is an HTTP server that answers a small set of GET requests. Point GOPROXY at it for every developer and CI job, set GONOSUMDB for your own module prefix, and have it refuse modules you have not approved. Then builds can only pull code that passed through your control point.

What is a module proxy, technically?

The Go reference defines it simply: an HTTP server that responds to GET requests for specific paths, with no query parameters and no special headers. It follows that even a site serving from a fixed file system, including a file:// URL, can be a proxy. A 200 response is success, redirects are followed, and 4xx or 5xx responses are errors. The codes 404 and 410 mean the module or version isn't available here but may exist elsewhere.

For a module such as example.com/gopher at v1.0.0, the client requests the .info, .mod and .zip files under a path for that module and version. The reference states that a minimal implementation could serve files from a module cache directory and use go mod download, with suitable configuration, to fetch missing modules. The reference notes that several implementations of proxy servers already exist.

Which setup fits an organisation?

The Go documentation describes two patterns.

A proxy that serves everything. The reference calls this the most control for administrators with the least configuration per developer. Developers set two variables:

GOPROXY=https://proxy.corp.example.com
GONOSUMDB=corp.example.com

With a single URL, the go command will not connect to any other proxy or to version control. GONOSUMDB tells it not to check the public checksum database for your own prefix, which would otherwise fail because that database cannot see your private code. The proxy needs read access to your private repositories and internet access for new public versions.

A proxy that serves only private modules. Here the variable lists several sources: your proxy first, then the public proxy, then direct. The go command tries them in order.

The related variables are GOPRIVATE, a list of glob patterns for private path prefixes that acts as the default for GONOPROXY and GONOSUMDB, and GOINSECURE for modules fetched over HTTP, which you should avoid.

How does a proxy become a gatekeeper?

The reference has a sentence that matters here. List entries are separated by commas or pipes. After a comma, the go command falls back to later sources only on a 404 or 410. After a pipe, it falls back after any error, including timeouts. The documentation says this lets a proxy act as a gatekeeper for unknown modules: it can answer 403 Forbidden for modules not on an approved list.

That is the policy lever. With a comma and a 403 for unapproved modules, the client does not fall through to the public internet, so the build fails and a human decides. You could combine this with an allowlist of module paths, a minimum age for new versions, and a block list from advisories.

Mind the consequence of the pipe form: a timeout on your proxy would send clients to the next source, which defeats the control. Use the comma form for gatekeeping.

A constructed example

A company runs a proxy behind its network. GOPROXY is set in the CI base image and in the developer setup script, with the single URL and nothing else. A new dependency request arrives as a pull request that adds an entry to the allowlist file the proxy reads. A reviewer checks the module's owner, history and licence, then merges. Until then, go build fails for that module with the 403 message, and nobody has to remember a rule.

What to do

  1. Choose the model: one proxy for everything, or private modules only with fallback.
  2. Stand up a proxy that implements the protocol and has read access to private repositories.
  3. Set GOPROXY, GOPRIVATE and GONOSUMDB in CI images and developer setup, with the comma form.
  4. Return 403 for modules not on your approved list, and log every denial.
  5. Keep the go.sum file committed so integrity checks still apply to public modules.
  6. Block direct access to the public proxy and to version control from build networks.
  7. Back up the proxy storage, and plan what happens if it is unavailable.
  8. Review the allowlist on a schedule and remove modules you no longer use.

Safeguard dependency inventory can show what your builds actually pull, see the dependency inventory demo.

Quick check

Why set GONOSUMDB for private modules?

The public checksum database cannot see private code, so checking against it fails. GONOSUMDB tells the go command to skip that check for your prefix.

Should a gatekeeper proxy use commas or pipes in GOPROXY?

Commas. A comma falls back only on 404 or 410, so a 403 stops the build. A pipe falls back on any error, including timeouts.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.