skip to content

What do GOFIPS140 at build time and GODEBUG=fips140 at run time each control in a Go binary?

level: middleimportance: should knowfreq 45%

answer

  1. two switches, not one
  2. build time versus process start
  3. the build one also pins a code snapshot
  4. on routes and self-checks; only rejects
  5. both default to off

basics

~10 s

GOFIPS140 is a build setting: it selects which snapshot of the Go Cryptographic Module is compiled in and makes fips140=on the binary's default GODEBUG. GODEBUG=fips140 then picks the run-time posture: off, on, or only.

solid answer

~40 s

Two layers. At build time `GOFIPS140` (default `off`) does two things: it substitutes a frozen, externally validated snapshot of the Go Cryptographic Module for the toolchain's live copy of that source, and it stamps `fips140=on` into the binary's default GODEBUG. `GOFIPS140=latest` turns the mode on but keeps the current source tree, so only a pinned version like `GOFIPS140=v1.0.0` gives you a validated snapshot. At run time `GODEBUG=fips140=on` routes standard-library cryptography through that module and runs an integrity self-check over its code and data at load; `fips140=only` additionally makes every non-approved standard-library crypto call fail. Code can read the state through `crypto/fips140.Enabled()` and, since Go 1.26, `Enforced()` and `Version()`. `go version -m` on the shipped binary prints the recorded `GOFIPS140` and `DefaultGODEBUG` settings.

code

text · 7 lines
text
GOFIPS140=v1.0.0 go build -o svc ./cmd/svc

go version -m svc
#	build	GOFIPS140=v1.0.0
#	build	DefaultGODEBUG=fips140=on

GODEBUG=fips140=only ./svc   # strict: non-approved calls now fail

go deeper

for a junior

Be ready to say that Go has a built-in FIPS 140-3 mode and that it is switched on by a build setting plus a GODEBUG, not by importing some special package into your code.

for a middle

Explain both layers precisely: what GOFIPS140 changes about the build, that off is the default on both switches, and how fips140=on differs from fips140=only at run time.

for a senior

Show that the mode is fixed at process start and self-checked at load, that only a pinned snapshot gives you a validated module, and that the build settings are recorded in the artefact.

for a principal

Own the policy: which artefacts are built this way, which snapshot is pinned, and how the recorded build metadata becomes the evidence an auditor asks you for.

## Two switches, deliberately separated FIPS 140-3 is a US federal standard for cryptographic modules. Since Go 1.24 the Go toolchain ships its own **Go Cryptographic Module** — the code under `crypto/internal/fips140`, written in Go, no C library involved — and gives you two independent controls over it: one at build time and one at process start. Confusing them is the most common mistake in this area, so take them one at a time. ### GOFIPS140 — a build setting `GOFIPS140` is read by the `go` command when it builds. Its default is `off`, which means the build behaves exactly as it always has. Any other value does **two** things at once: 1. **It picks which copy of the cryptographic source is compiled in.** With `GOFIPS140=latest` you get the toolchain's current tree. With a frozen version such as `GOFIPS140=v1.0.0` the `go` command unpacks a snapshot shipped in `GOROOT/lib/fips140` (module zips) into the module cache and compiles that instead. The reason snapshots exist is that external lab validation and certification apply to a specific, frozen copy of the code — not to whatever the tip of the Go tree happens to contain this month. So `latest` enables the mode without giving you a validated module, and a regulated build wants the pinned version. 2. **It stamps the default GODEBUG.** A binary built with any non-`off` `GOFIPS140` carries `fips140=on` as its default GODEBUG, so it starts in FIPS mode without anybody setting an environment variable at deploy time. Both facts are recorded in the binary. `go version -m ./svc` prints the build settings, and among them you will see `GOFIPS140=v1.0.0` and `DefaultGODEBUG=fips140=on`. The same data is available to the program itself through `runtime/debug.ReadBuildInfo`. That recorded metadata is what turns "we build this service in FIPS mode" from a claim into evidence. ### GODEBUG=fips140 — a run-time setting GODEBUG is Go's general mechanism for run-time behaviour toggles, read from the environment (layered over the binary's compiled-in default) once, at startup. The `fips140` setting takes three values: - **`off`** — normal behaviour. - **`on`** — cryptography runs through the Go Cryptographic Module, and the module verifies itself at load time. The linker lays the module's code and data out contiguously and records a hash of them; at startup the module re-hashes those sections and compares. If the check fails the program panics rather than proceeding with unverified cryptography. Non-approved algorithms still work in this mode. - **`only`** — everything `on` does, plus **strict enforcement**: calls into non-approved standard-library cryptography now fail. Where the API can return an error it returns one; where it cannot, it panics. The mode cannot be changed after the process starts — the self-check has to run before crypto package initialisation, so there is nothing to flip later. `crypto/fips140.Enabled()` (Go 1.24) reports whether the mode is on, and its documentation states the value cannot change during the run. Go 1.26 added `crypto/fips140.Enforced()`, which distinguishes strict `only` mode, `Version()`, which returns the module version in use, and `WithoutEnforcement(f func())`, which relaxes strict enforcement for the duration of one tightly scoped function. ### How they combine in practice The common recipe for a regulated service is to set `GOFIPS140` to a pinned snapshot in the release pipeline, so the shipped artefact defaults to FIPS mode, and to raise the environment to `GODEBUG=fips140=only` when the deployment must reject non-approved algorithms outright. Nothing stops you setting `GODEBUG=fips140=on` on a binary built with `GOFIPS140=off` — the run-time setting works on its own — but then you are running the toolchain's current cryptographic source rather than a validated snapshot, which is usually not what an auditor is asking about. One boundary worth being clear about: neither switch certifies your deployment. They select and enable a module that has been validated, and they constrain what the standard library will do. Whether your system as a whole satisfies a certification regime is a question about the regime and your security policy, not about these two environment variables. ### The short version to say out loud `GOFIPS140` is compile-time and does two jobs: choose the module snapshot, set the binary's default GODEBUG. `fips140` as a GODEBUG is run-time and does one: choose between off, on and strict `only`. Default is `off` on both. The state is readable at run time through `crypto/fips140` and inspectable on the artefact with `go version -m`.

  • What does GOFIPS140=latest give you that a pinned version like v1.0.0 does not, and why does that matter for a regulated build?
    `latest` turns the mode on but compiles the toolchain's current cryptographic source, which carries no external validation and changes with every Go release. A pinned snapshot such as `v1.0.0` unpacks a frozen module zip shipped in `GOROOT/lib/fips140` — the copy that went through lab validation. For an audit you want the pinned one, so the binary's recorded `GOFIPS140` setting names a version the security policy actually covers.
  • Can a running Go program turn FIPS 140-3 mode on or off?
    No. The `fips140` GODEBUG is read once at startup, before crypto package initialisation, because the Go Cryptographic Module runs an integrity self-check over its own code and data at load. `crypto/fips140.Enabled()` reports the state and its documentation says it cannot change after the program has started. The only scoped escape hatch is `fips140.WithoutEnforcement(f)`, added in Go 1.26, which relaxes strict enforcement for the duration of `f`.
  • What happens at startup if the compiled-in Go Cryptographic Module fails its integrity check?
    Initialisation panics rather than continuing with unverified cryptography. The linker lays the module's code and data out contiguously and records a hash; at startup the module re-hashes those sections and compares, and `crypto/fips140.Enabled()` panics if the mode is on but verification did not pass. In practice this fires when something has rewritten, stripped or patched the binary after linking.

It is a factory setting plus an operating switch: the build decides which certified parts go into the machine, and the run-time setting decides whether the machine refuses non-approved work.

saying these in an interview costs you the question

  • Thinks setting GOFIPS140 alone certifies the deployment
  • Believes the fips140 GODEBUG can be flipped while running
  • Says GOFIPS140 defaults to on in recent Go
  • Confuses fips140=on with the strict only mode
  • Assumes FIPS mode needs cgo or a C library