How do you make the go command fetch a private module from an internal Git host?
answer
- the default services are public ones
- your internal path gets sent there
- glob patterns over module path prefixes
- one variable, two narrower lists
- credentials belong to git, not go
basics
~20 sSet GOPRIVATE to glob patterns matching your internal module path prefixes. Matching modules are then fetched directly from version control instead of the public proxy, and skipped by the public checksum database. Credentials remain git's job.
solid answer
~50 sBy default the go command sends every module path to the public proxy and, for unrecorded versions, to the public checksum database — so an internal path like `git.corp.example.com/team/svc` is both leaked and unresolvable, and the build fails. The fix is `GOPRIVATE=git.corp.example.com/*` (comma-separated glob patterns over module path prefixes). GOPRIVATE sets the defaults for two narrower lists: the paths that bypass GOPROXY and are fetched directly from version control, and the paths that are never looked up in the checksum database. Set GONOPROXY explicitly instead when an internal mirror should still serve those modules. Authentication is not the go command's job — it invokes `git`, so credentials come from a `url.<base>.insteadOf` rewrite to SSH, a credential helper or a netrc file. Note that go.sum entries are still written and still enforced for private modules; only the public database lookup is skipped.
code
text · 9 lines# module path prefixes that must never go to public services
$ go env -w GOPRIVATE='git.corp.example.com/*,github.com/acme-corp/*'
# credentials are git's job, not the go command's
$ git config --global url."ssh://[email protected]/".insteadOf \
"https://git.corp.example.com/"
# confirm what the go command will actually do
$ go env GOPROXY GOPRIVATE GONOPROXY GOSUMDBgo deeper
Be ready to say that a private module needs configuration because the default proxy and checksum database are public services that cannot reach an internal host. Naming GOPRIVATE as the variable is enough at this level.
Explain that GOPRIVATE is glob patterns over module path prefixes and that it sets defaults for two narrower behaviours: bypassing the proxy and skipping the checksum database. Be clear that authentication is the version-control tool's job.
Show the diagnosis path on a failing CI runner: print the effective values with go env, confirm the pattern covers the path, then reproduce the clone with git. Explain why a git auth error after setting GOPRIVATE is progress rather than a new problem.
Own the choice between direct-to-VCS and an internal mirror for private code, including where credentials live, what the checksum-database exemption actually costs in guarantee, and why the exemption is scoped by pattern rather than switched off wholesale.
## What goes wrong without configuration Out of the box the go command treats every module path the same way: ask `GOPROXY` for it, and if the version is not already recorded in `go.sum`, ask `GOSUMDB` — by default the public checksum database at `sum.golang.org` — for its authoritative hash. For an internal module path this fails twice over: 1. **It leaks.** The module path, which usually contains your internal host name, your team name and your service name, is sent to a public service as a lookup. Nothing secret is *served*, but the existence of the path is disclosed. 2. **It fails.** Neither public service can reach your internal host, so the fetch or the hash lookup errors out and the build stops — often with a confusing 404 that points at the public proxy rather than at your host. ## GOPRIVATE `GOPRIVATE` is a comma-separated list of glob patterns matched against **module path prefixes** (path element wildcards, not regular expressions): ``` GOPRIVATE=git.corp.example.com/*,github.com/acme-corp/* ``` It is not itself a mechanism; it is a convenience that supplies the default for two narrower variables: - the **no-proxy** list — matching paths bypass GOPROXY entirely and are fetched directly from version control; - the **no-checksum-database** list — matching paths are never looked up in GOSUMDB. Setting GOPRIVATE is the right first move because those two lists almost always want the same patterns. Set the no-proxy list explicitly (`GONOPROXY`) when you want to diverge — most commonly when an internal mirror *does* serve your private modules, so they should keep going through GOPROXY while still skipping the public checksum database. A pattern matches a path prefix at element boundaries, so `github.com/acme-corp/*` covers every repository under that organisation but not `github.com/acme-corp-public/...`. Patterns are matched against **module paths**, not host names of proxies, and not import paths of individual packages. ## What GOPRIVATE does not do Three things it is frequently believed to do, and does not: - **It does not authenticate anything.** With the proxy bypassed, the go command shells out to the version-control tool. Whether that clone succeeds is entirely a matter of `git` configuration: an `insteadOf` rewrite from HTTPS to SSH, a credential helper, or a netrc entry for the host. A common CI failure is GOPRIVATE set correctly and no credentials on the runner — the symptom changes from a proxy 404 to a git authentication error, which is progress. - **It does not disable hashing.** Private modules still get `go.sum` lines, written on first fetch from the content the go command received, and enforced on every fetch afterwards. What is skipped is only the *external* lookup that would otherwise establish the authoritative value. That is a real reduction in guarantee — for private code you are trusting first-fetch content and your own repository history rather than a public transparency log — and it is the reason the exemption should be scoped to patterns rather than switched on globally. - **It does not make an insecure host work.** Relaxing transport requirements for a host is a separate setting (`GOINSECURE`), and restricting which version-control tools may be used for which paths is another (`GOVCS`). Reach for those deliberately, not as part of the private-module recipe. ## The two shapes an organisation ends up with **Direct-to-VCS.** `GOPRIVATE` covers the internal prefixes; those modules are cloned from the internal Git host; everything else flows through the public mirror. Cheapest to set up. The cost is that every build machine now needs Git credentials for the internal host, and the internal host is in the hot path of every build that imports internal code. **Internal mirror.** An internal proxy serves both third-party and internal modules. GOPROXY points at it; the no-checksum-database list still covers internal prefixes; build machines need no VCS credentials at all. More to operate, but credentials live in one place instead of on every runner, and you get one point to cache and audit. ## Verifying what actually happened When a private fetch misbehaves, resolve the configuration before theorising: - `go env GOPROXY GOPRIVATE GONOPROXY GOSUMDB` prints the values in effect. Anything set previously with `go env -w` persists in the go environment file and outlives every shell, which is why a developer machine and a CI runner so often disagree for reasons the repository cannot explain. - Confirm the pattern actually covers the module path, prefix and all. A pattern written for the repository URL rather than the module path is the classic near-miss. - Then test the clone by hand with `git` for the same URL. If that fails too, the problem was never the go command.
- When would you set GONOPROXY explicitly rather than relying on GOPRIVATE?When an internal mirror serves your private modules. GOPRIVATE would make those paths bypass GOPROXY and go straight to the internal Git host, defeating the mirror and putting VCS credentials back on every build machine. Setting GONOPROXY to something narrower — often empty — keeps private modules flowing through the mirror while the checksum-database exemption still applies.
- GOPRIVATE is set correctly but CI now fails with a git authentication error. Is that progress?Yes. The failure moved from the public proxy to the internal host, which proves the pattern matched and the bypass took effect. What remains is credentials on the runner — an SSH deploy key with an `insteadOf` rewrite, a credential helper, or a netrc entry. The go command never authenticates on its own; it delegates entirely to the version-control tool.
- Do private modules still get go.sum entries?Yes. Hashes are recorded on first fetch and enforced on every fetch afterwards, exactly as for public modules. Only the lookup against the public checksum database is skipped, so the authoritative value comes from what you first downloaded and then committed rather than from an external log. That is the actual guarantee you traded away, and it is worth stating explicitly in review.
saying these in an interview costs you the question
- Thinks GOPRIVATE supplies credentials for the internal host
- Believes private modules get no go.sum entries at all
- Turns the checksum database off globally instead of by pattern
- Writes the repository URL instead of the module path prefix
- Assumes the go command detects internal hosts automatically