What Is the go.sum File and Why You Should Commit It

The go.sum file stores dependency checksums to ensure build reproducibility and security, so you must commit it to your repository.

The checksum lock

You clone a repository. You run the code. It works. Your teammate runs the code on a fresh machine. The build fails with a checksum mismatch. Or worse, the build succeeds, but a dependency was silently swapped for a version that logs sensitive data to a remote server. The go.sum file is the mechanism that stops this from happening. It turns your dependency list into a cryptographic contract.

go.sum is not optional metadata. It is the integrity database for your module graph. Every time Go downloads a dependency, it computes a hash of the content and compares it against go.sum. If the hash matches, the build continues. If the hash differs, Go aborts. This protects against compromised proxies, corrupted downloads, accidental edits, and supply chain attacks where an attacker replaces a dependency with malicious code.

go.mod declares, go.sum verifies

go.mod and go.sum serve different roles. go.mod is the declaration. It lists the module path, the Go version, and the direct requirements. go.sum is the verification. It stores the cryptographic hashes for every module in the dependency tree, including transitive dependencies that you never imported directly.

Think of go.mod as a shopping list. It says "I need milk, eggs, and bread." go.sum is the receipt with the serial numbers of every item. It proves you got exactly what you asked for. If the store swaps the milk for a different brand, the serial number won't match, and you walk out of the store without buying anything.

The Go toolchain treats go.sum as the source of truth for content. You can change go.mod to request a new version, but go.sum ensures the downloaded content matches the version you requested. This separation allows the toolchain to detect when a module author retracts a version and replaces it with different content, or when a proxy serves stale data.

Minimal example

Here is a minimal module setup. The go.mod file declares a single dependency.

// go.mod
module example.com/myapp

go 1.21

require github.com/pkg/errors v0.9.1

Running go mod tidy generates go.sum. The file contains two entries for this one dependency.

// go.sum
github.com/pkg/errors v0.9.1 h1:FEBLx1zS2849cf0186B7aaV0FzdP1C6F4XS1g...
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5N+...

The first line hashes the module content. The h1: prefix indicates a SHA-256 hash encoded in base64. The second line hashes the dependency's own go.mod file. This protects the dependency graph structure. If the dependency changes its requirements, the hash changes, and Go detects the modification.

What happens at build time

When you run go build, the toolchain follows a strict path. It reads go.mod. It downloads the module from the proxy. It computes the hash of the downloaded content. It compares that hash against go.sum.

If go.sum is missing the entry, Go adds it automatically. This is why go.sum grows as you add imports. If the entry exists but the hash differs, Go aborts the build. The compiler rejects the program with verifying github.com/pkg/errors@v0.9.1: checksum mismatch: .... This error is a hard stop. You cannot ignore it without changing environment variables or editing go.sum, which defeats the purpose.

The community treats go.sum as a generated file that must be committed to version control. You never edit go.sum by hand. You change go.mod or your imports, then run go mod tidy to update the checksums. This convention ensures every developer and CI system builds the project with identical dependency content.

The hidden go.mod hash

Every module in go.sum has two entries. One for the module content, one for the module's go.mod file. The go.mod hash is often overlooked. It protects against a subtle class of bugs where a dependency updates its go.mod to pull in a new version of a transitive dependency.

Imagine module A depends on module B. Module B depends on module C version 1.0.0. If the author of B pushes a new go.mod that changes the requirement to C version 2.0.0, the hash of B's go.mod changes. go.sum catches this change. Go stops the build and forces you to review the update. This prevents a dependency from silently altering its requirements and breaking your build or introducing a vulnerability.

This mechanism is crucial for diamond dependencies. If A depends on B and D, and both B and D depend on C, go.sum ensures C is consistent across the graph. The go.mod hashes guarantee that B and D are using the versions of C they expect.

Realistic dependency tree

Real projects have deep dependency trees. A single import can pull in dozens of transitive modules. go.sum tracks every single one.

// main.go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    // Gin pulls in net/http, encoding/json, and many others.
    // go.sum will contain hashes for every single one of those.
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })
    // Start server on port 8080.
    r.Run(":8080")
}

This code imports gin. Gin depends on net/http, encoding/json, github.com/gin-gonic/gin, github.com/go-playground/validator, and many others. go.sum will contain hashes for all of them. If validator updates its go.mod to require a new version of a sub-dependency, the hash of validator's go.mod changes. go.sum catches that change. This prevents a dependency from silently altering its requirements and breaking your build or introducing a vulnerability.

The go.sum file can grow large. It is normal for a production application to have hundreds or thousands of lines in go.sum. Do not trim it manually. go mod tidy removes unused entries automatically. If you delete an import, go mod tidy cleans up the checksums. This keeps the file lean and accurate.

Pitfalls and private modules

Skipping go.sum in version control breaks reproducibility. If you commit go.mod but not go.sum, the next developer might download a different version of a module if the proxy has changed, or if a module author retracts a version and replaces it. The build might succeed with different content than you tested. This leads to "it works on my machine" bugs that are impossible to debug.

Private modules can cause friction. The public sum database cannot verify private modules. You get verifying github.com/internal/private@v1.0.0: module ...: reading ...: sum not found. You have two options. Use GONOSUMCHECK to skip verification for specific modules, or host your own sum database.

If you use GONOSUMCHECK, document it in your repository. The team needs to know why verification is disabled. This environment variable tells Go to skip checksum verification for modules matching the pattern. It reduces the security guarantee. Use it only when necessary.

# Skip verification for private modules.
# Document this in your README or CI configuration.
export GONOSUMCHECK=github.com/myorg/private-*

Another pitfall is editing go.sum to fix a mismatch. If the hash differs, the content differs. Editing the hash to match the content hides the problem. The correct fix is to update the dependency version in go.mod and run go mod tidy. This generates the correct hash for the new content.

Decision matrix

Use go.sum to lock dependency content to cryptographic hashes for reproducible builds. Use go.mod to declare the module path, Go version, and direct requirements. Use go mod tidy to synchronize go.mod and go.sum with your actual imports. Use GONOSUMCHECK only when private modules prevent verification and you accept the risk of unverified content. Use GONOSUMDB to skip the public sum database lookup when you maintain your own verification infrastructure.

Commit go.sum. Trust the hash.

Where to go next