skip to content

Module Proxy, go.sum and Private Modules

Where the go command actually fetches code from, and how it proves the bytes never changed: a caching proxy, per-module hashes in go.sum, and a public checksum database. Teams with internal repos care most about the private side — GOPRIVATE plus an in-house proxy.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

5

What does a go.sum line record, and how is go.sum different from go.mod?

level: middleimportance: must knowfreq 66%

answer

  1. two files, two different jobs
  2. content, not version selection
  3. usually two lines per module version
  4. one of them ends in /go.mod
  5. graph readable without full downloads

basics

~10 s

go.sum records expected content hashes, normally two per module version: one over the module's file tree, one over its go.mod alone. It selects no versions, and a mismatch fails the build.

solid answer

~50 s

`go.mod` states requirements; the go command resolves them into a build list. `go.sum` is the verification list for that resolution: for each module version it records an `h1:` hash of the module's file tree and a second `h1:` hash of that version's `go.mod` alone, so the dependency graph can be read without downloading full source. On every fetch the go command hashes what it received and refuses to build on a mismatch. It is not a lock file — it neither chooses nor pins versions, and it commonly holds hashes for versions in the module graph that were not selected, so a later change of selection is still verifiable. It must be committed. `go mod verify` re-hashes what is already in the local module cache against these lines and reports whether anything changed after download.

code

text · 5 lines
text
# hash over the module's whole file tree
golang.org/x/text v0.14.0 h1:<base64 hash of the module file tree>

# hash over that version's go.mod alone, so the graph can be read cheaply
golang.org/x/text v0.14.0/go.mod h1:<base64 hash of that go.mod>

go deeper

for a junior

Be ready to say that go.mod declares requirements and go.sum records expected hashes, and that both are committed. Knowing that a mismatch fails the build rather than warning is the point to land.

for a middle

Explain the two lines per module version and why the go.mod hash exists separately — cheap graph resolution without downloading source. Interviewers here also want the clear statement that go.sum selects nothing.

for a senior

Demonstrate the operational reflexes: what a mismatch means as an incident, why editing or deleting the line is never the fix, and what go mod verify actually proves about a machine offline versus what it cannot prove.

for a principal

Own how go.sum is reviewed at organisational scale — noticing unexpected new module paths in a diff, deciding who is allowed to add dependencies, and where content verification sits relative to your other supply-chain controls.

## Two files, two jobs A Go module has two checked-in metadata files, and confusing them is the most common misunderstanding in this area. - **`go.mod`** declares identity and requirements: the module path, the language version, and `require` lines naming other modules and minimum versions. From the `go.mod` files of every module in the graph, the go command computes a **build list** — one selected version per module. - **`go.sum`** declares nothing about versions. It is a list of **expected content hashes**. Its only job is to answer: "the bytes I just downloaded for module M at version v — are they the same bytes everyone else got?" So `go.mod` answers *which*, `go.sum` answers *whether the content is unchanged*. ## The shape of a line A go.sum entry looks like: ``` <module path> <version> h1:<base64> <module path> <version>/go.mod h1:<base64> ``` Two lines per module version, normally: 1. **The tree hash** — a hash over the module's whole file list and contents (the zip's file tree). 2. **The go.mod hash** — a hash over just that version's `go.mod`, marked by the `/go.mod` suffix on the version. Why separate them? Because building the module graph only needs everyone's `go.mod`, not their source. The go command downloads and verifies just the `.mod` files while resolving, and pulls full zips only for modules it actually compiles. Without the separate go.mod hash, reading the graph would mean downloading every candidate module in full. The `h1:` prefix names the hash algorithm — the current, SHA-256-based module hashing scheme. It is a prefix, not a truncation: a future scheme would appear under a new prefix and old lines would remain readable. ## Why it is not a lock file In many ecosystems the lock file both **pins** versions and **verifies** them. In Go those responsibilities are split, and that surprises people arriving from elsewhere. - Deleting a version from `go.sum` does not change which version is built; it just means the next fetch has nothing to check against (and the go command will refuse rather than trust silently). - `go.sum` regularly contains **more** than the build list. Hashes for versions considered during graph resolution but not selected are kept, so that a change upstream that shifts selection is still verifiable without a fresh trust decision. - Reproducibility of *versions* comes from `go.mod` plus minimal version selection, which is deterministic on its own. `go.sum` adds reproducibility of *content*. ## What happens on a mismatch If the go command downloads a module version whose hash does not match the recorded line, the build stops with a verification error. It does not warn and continue, and it does not update the line. That is deliberate: a differing hash means the content behind a version you already trusted is not what it was, and the only safe reaction is to stop. The inverse case — no line at all — is different and benign. A brand-new dependency has no recorded hash, so the go command consults the checksum database for the authoritative value, records it, and proceeds. That is why adding a dependency writes new go.sum lines while upgrading a compromised one fails. ## Keeping it tidy `go mod tidy` rewrites both files together: it drops requirements nothing imports and prunes go.sum lines that are no longer reachable, while keeping the entries needed to build and test the packages in your module. A go.sum that has drifted — lines for modules long removed — is a sign that tidy has not been run, not a security problem in itself. Because the file is append-heavy and machine-written, it produces noisy diffs. Review it for *unexpected new module paths* rather than trying to read the hashes; a new path appearing in a change that claimed to touch nothing is the signal worth catching in review. ## `go mod verify` `go mod verify` is the offline half of the story. It walks the modules already extracted in the local module cache, recomputes their hashes, and compares them with `go.sum`, printing `all modules verified` when everything matches. It answers "has anything on this machine been modified since it was downloaded?" — an edit in the cache, a partially written file, a stray tool. It does not contact the network, does not consult the checksum database, and says nothing about vulnerabilities. ## Committing it `go.sum` belongs in version control, always. Without it, each machine accepts whatever bytes it happened to receive the first time it fetched a dependency, and the whole verification chain evaporates. A repository whose `.gitignore` lists `go.sum` has disabled its own integrity checking.

  • Why does go.sum need a separate hash for a module version's go.mod?
    Because resolving the module graph reads every candidate's `go.mod` but compiles only some of them. With a hash for the `go.mod` on its own, the go command can download and verify those small files alone while working out the build list, and fetch full module zips only for what it actually builds. Without it, graph resolution would mean downloading everything.
  • Why can go.sum contain versions that the build never uses?
    Graph resolution considers requirements from every module in the graph, including versions that lose to a higher requirement elsewhere. Recording their hashes means that if an upstream change later shifts which version wins, the content is already verifiable without a new trust decision. It is one reason go.sum is not a lock file — it is deliberately broader than the build list.
  • A teammate 'fixed' a verification failure by deleting the go.sum line. What went wrong?
    They removed the evidence rather than the problem. With no recorded hash, the go command treats the module as new, takes an authoritative hash from the checksum database, and writes a fresh line — so the build goes green while the question of why the content changed is never answered. A mismatch on a version you already trusted is an escalation, not a file edit.

saying these in an interview costs you the question

  • Calls go.sum a lock file that pins dependency versions
  • Thinks deleting a go.sum line is a valid fix for a mismatch
  • Says go.sum should be gitignored because it is generated
  • Believes every go.sum line corresponds to a selected build-list version
  • Confuses go mod verify with a vulnerability scan
open as a page

What is GOPROXY, and where does the go command fetch modules from by default?

level: juniorimportance: should knowfreq 58%

basics

~20 s

GOPROXY lists the module servers the go command downloads dependencies from. It defaults to https://proxy.golang.org,direct: try Google's public module mirror first, and if the module is not there, fetch it straight from its version-control origin.

open as a page

How do you make the go command fetch a private module from an internal Git host?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Set 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.

open as a page

How do you decide between proxy.golang.org and an internal Go module mirror for a build fleet?

level: principalimportance: should knowfreq 38%

basics

~20 s

Decide on egress policy, availability and ownership. The public mirror is free, durable and highly available but puts every build on the public internet; an internal mirror buys allowlisting and one credential boundary, and becomes a company-wide single point of failure.

open as a page

What is in the Go module cache (GOMODCACHE), and when is go clean -modcache the right fix?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

GOMODCACHE holds every downloaded module version, extracted read-only, plus the raw files as fetched. Clearing it forces a fresh download: the right fix for a corrupted local cache, and no fix for a checksum mismatch.

open as a page