Introduction

When CGO_ENABLED=1, Go uses the system C compiler (gcc or clang) to compile C code in cgo-enabled packages like database/sql drivers, net, and any package using import "C". Cross-compiling (e.g., building linux/amd64 from macOS/arm64) requires a cross-compiler for the target architecture. Without it, the linker fails with cryptic errors.

Symptoms

  • gcc: error: unrecognized command-line option '-m64'
  • ld: unknown option: --build-id=none
  • # runtime/cgo: gcc: error: unrecognized argument: '-marm'
  • exec: "x86_64-linux-gnu-gcc": executable file not found in $PATH
  • clang: error: unable to execute command: Segmentation fault

``` $ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build ./cmd/server # runtime/cgo cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH

$ CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build . # runtime/cgo xcrun: error: unable to find utility "clang", not a developer tool or in PATH ```

Common Causes

  • Cross-compiling from macOS to Linux without cross-compiler
  • Building ARM64 binary on x86_64 machine without aarch64-linux-gnu-gcc
  • Docker build from multi-arch base images without gcc
  • CI runner missing cross-compilation toolchain
  • CGO_ENABLED defaults to 1 on macOS but target is Linux

Step-by-Step Fix

  1. 1.Disable CGO if not needed (simplest fix):
  2. 2.```bash
  3. 3.# Most Go packages don't need CGO
  4. 4.CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./cmd/server

# Pure Go binary - works on any platform file ./server # ELF 64-bit LSB executable, x86-64, statically linked ```

  1. 1.Install cross-compiler on Ubuntu/Debian:
  2. 2.```bash
  3. 3.# For linux/amd64 from any host
  4. 4.sudo apt install -y gcc-x86-64-linux-gnu
  5. 5.CC=x86_64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build

# For linux/arm64 from x86_64 sudo apt install -y gcc-aarch64-linux-gnu CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build

# For linux/arm/v7 sudo apt install -y gcc-arm-linux-gnueabihf CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build ```

  1. 1.Cross-compile from macOS:
  2. 2.```bash
  3. 3.# Install cross-compiler via Homebrew
  4. 4.brew install FiloSottile/musl-cross/musl-cross

# Build linux/amd64 with musl (static binary) CC=x86_64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "-extldflags=-static"

# Build linux/arm64 CC=aarch64-linux-musl-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-extldflags=-static" ```

  1. 1.Use Docker for reliable cross-compilation:
  2. 2.```dockerfile
  3. 3.FROM golang:1.22-bookworm AS builder
  4. 4.WORKDIR /app
  5. 5.COPY go.mod go.sum ./
  6. 6.RUN go mod download
  7. 7.COPY . .
  8. 8.# Build inside target-compatible container
  9. 9.RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
  10. 10.go build -ldflags="-s -w" -o server ./cmd/server

FROM debian:bookworm-slim COPY --from=builder /app/server /server CMD ["/server"] ```

  1. 1.Use zig as universal C cross-compiler:
  2. 2.```bash
  3. 3.brew install zig

# zig cc acts as a drop-in replacement for any target CC CC="zig cc -target x86_64-linux-gnu" CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build CC="zig cc -target aarch64-linux-gnu" CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build ```

Prevention

  • Set CGO_ENABLED=0 by default unless CGO is explicitly needed
  • Use -ldflags "-s -w" to strip debug info and reduce binary size
  • In CI, install cross-compilers as part of the build image
  • Use go tool dist list to see supported OS/ARCH combinations
  • Test cross-compilation locally before pushing to CI
  • Use file command to verify binary architecture after build