The public proxy assumption
You clone a repository from your company's internal GitLab, run go build, and the terminal throws a checksum mismatch or a 404 error. You did not mistype the import path. Your network connection is fine. The Go toolchain simply assumed your dependency lives on the public internet and tried to fetch it from proxy.golang.org.
Go's module system was designed around a public, open-source ecosystem. By default, every go get and go build routes through the official module proxy and verifies downloads against the checksum database. This design gives you fast downloads, built-in caching, and protection against supply-chain attacks. It also breaks the moment you introduce a repository that requires authentication or lives behind a corporate firewall.
Private modules exist outside that public trust model. They require direct access to version control, they should never be cached by a public proxy, and they cannot be verified against a global checksum database. The Go toolchain provides environment variables and configuration directives to tell it exactly which paths to treat differently.
How Go decides what is private
Go uses a pattern-matching system to separate public from private code. The primary switch is GOPRIVATE. When you set this variable, you give the toolchain a list of import path patterns. Any module whose path matches one of those patterns gets special treatment: the proxy is skipped, the checksum database is bypassed, and the toolchain reaches directly to the version control system.
Think of GOPRIVATE as a whitelist for direct network access. The proxy is the default route. The whitelist is the exception. If a module path does not match the whitelist, Go assumes it is public and enforces the standard security and caching pipeline.
The pattern syntax uses simple glob matching. A trailing /* matches the path and all subpaths. A bare domain matches only that exact domain. You can set multiple patterns separated by commas, spaces, or newlines. The toolchain evaluates them in order and stops at the first match.
Module paths should always match the actual VCS URL. If your repository lives at gitlab.company.com/team/auth, your go.mod should declare module gitlab.company.com/team/auth, and your GOPRIVATE pattern should cover gitlab.company.com/*. Mismatched paths are the most common cause of silent failures.
Configuring the toolchain
Here is the simplest way to tell Go to treat a domain as private. You set the environment variable, then run your build or fetch command.
// go.mod
module example.com/myapp
go 1.22
require example.com/internal/lib v1.2.0
# Tell the toolchain to skip the proxy for this domain
export GOPRIVATE="example.com/internal/*"
# Fetch the module directly from the VCS host
go get example.com/internal/lib@v1.2.0
When you run go get, the toolchain checks the import path against GOPRIVATE. The pattern matches, so Go skips proxy.golang.org entirely. It constructs the VCS URL, applies your local authentication credentials, downloads the tarball, and writes the module to your local cache at ~/go/pkg/mod.
You can make the configuration permanent by adding the export line to your shell profile, or by using the built-in environment writer. The go env -w command writes to ~/go/env and applies automatically to every future invocation.
# Persist the configuration across terminal sessions
go env -w GOPRIVATE="example.com/internal/*"
The go env approach is the standard convention. It keeps your shell configuration clean and makes the Go-specific settings discoverable with go env. Most teams document the required GOPRIVATE patterns in their onboarding README so new developers can run a single command and start building.
The checksum database and verification
Skipping the proxy is only half the equation. Go also maintains a global checksum database at sum.golang.org. Every public module download is verified against a cryptographic hash stored in that database. If the hash does not match, the build fails. This prevents tampered modules from entering your dependency tree.
Private modules cannot be verified against a public database. The hashes do not exist, and they should not exist. If you only set GOPRIVATE, Go will still try to verify the download against sum.golang.org and reject it. You need to tell the toolchain to skip verification for those paths as well.
The variable for this is GONOSUMCHECK. It uses the same glob pattern syntax as GOPRIVATE. In practice, you almost always set both variables to the same value.
# Skip both the proxy and the checksum database
export GOPRIVATE="example.com/internal/*"
export GONOSUMCHECK="example.com/internal/*"
You can also combine them into a single command for convenience. The toolchain treats them independently, so the order does not matter.
# Apply both exemptions in one step
go env -w GOPRIVATE="example.com/internal/*" GONOSUMCHECK="example.com/internal/*"
When both variables are set, the download pipeline changes completely. Go fetches the module directly, skips proxy caching, skips checksum verification, and writes the module to your local cache. The go.sum file will still record the hash, but it will be treated as a local reference rather than a public verification target.
Authentication and network flow
The Go toolchain does not manage authentication. It relies on your system's existing VCS credentials. If you use HTTPS, Go reads your .netrc file or prompts for credentials. If you use SSH, it relies on your ~/.ssh/config and loaded agent keys. The toolchain simply passes the URL to git, hg, or bzr and lets the version control client handle the handshake.
This separation is intentional. Go stays focused on module resolution and compilation. Authentication remains with the tools you already trust. It also means you can debug connection failures by running the underlying VCS command manually. If git clone works, go get will work. If git clone fails, the problem is your credentials or network policy, not Go.
Corporate firewalls sometimes block direct VCS access while allowing proxy traffic. In those cases, you can configure a private module proxy behind your firewall and point Go to it with GOPROXY. The internal proxy can handle authentication, caching, and compliance scanning. You still use GOPRIVATE to tell Go which paths should bypass the public proxy, but you replace the public proxy URL with your internal one.
# Route private modules through an internal proxy
export GOPROXY="https://proxy.internal.company.com,https://proxy.golang.org,direct"
export GOPRIVATE="example.com/internal/*"
The comma-separated GOPROXY value tells Go to try the internal proxy first, fall back to the public proxy, and finally attempt direct VCS access. This setup gives you caching and compliance without breaking private workflows.
Common failures and compiler messages
Private module configuration fails in predictable ways. The errors usually point to proxy routing, checksum verification, or authentication.
If you forget GOPRIVATE, the toolchain hits the public proxy and gets a 404. The terminal prints go: example.com/internal/lib@v1.2.0: reading https://proxy.golang.org/example.com/internal/lib/@v/v1.2.0.mod: 404 Not Found. The fix is adding the pattern to GOPRIVATE.
If you set GOPRIVATE but forget GONOSUMCHECK, the download succeeds but verification fails. You see go: example.com/internal/lib@v1.2.0: Get "https://sum.golang.org/lookup/example.com/internal/lib@v1.2.0": dial tcp: lookup sum.golang.org: no such host or a checksum mismatch error. The fix is adding the same pattern to GONOSUMCHECK.
If your module path does not match the GOPRIVATE pattern, Go treats it as public. A common mistake is setting GOPRIVATE=example.com when the actual path is example.com/team/auth. The bare domain does not match the subpath. You need GOPRIVATE=example.com/* or GOPRIVATE=example.com/team/*.
If authentication fails, the error comes from the VCS client, not Go. You might see fatal: could not read Username for 'https://gitlab.company.com': terminal prompts disabled when running in CI, or Permission denied (publickey) when using SSH. The toolchain cannot prompt for credentials in non-interactive environments. You must configure token-based auth or deploy SSH keys to the runner.
The go mod tidy command respects these variables. It will not attempt to fetch private modules through the proxy, and it will not complain about missing checksums if GONOSUMCHECK is set. If you run go mod tidy in a clean environment without the variables, it will fail or strip the private dependencies. Always set the environment before running module management commands.
Choosing the right configuration
Use GOPRIVATE when your module requires authentication or lives behind a firewall that blocks the public proxy. Use GONOSUMCHECK when you need to skip checksum verification for modules that are not registered in the public database. Use an internal GOPROXY when your organization requires centralized caching, compliance scanning, or network isolation. Use the default public proxy when you are building open-source software or consuming dependencies from GitHub, GitLab public, or Bitbucket public. Use replace directives in go.mod when you are patching a local copy for development, not when you are configuring network access.
Private modules are not a feature you enable. They are a network boundary you declare. The toolchain does what you tell it to do, and it does it consistently.