Fix 'exec: executable file not found in $PATH' When Using os/exec in Go

intermediateπŸ”· Go2026-06-11| Go 1.18+, Linux/macOS/Windows, systemd services, Docker containers

Error Message

exec: "gcc": executable file not found in $PATH
#go#os/exec#environment-variables#path

The Situation

Your Go program shells out to an external command β€” maybe gcc, ffmpeg, git, or some CLI tool. It runs fine in your terminal. Deploy it as a systemd service, drop it in Docker, or hand it off to CI β€” and suddenly:

exec: "gcc": executable file not found in $PATH

Same binary. Same machine. Different environment. The binary didn't disappear β€” your process just can't see it.

What's Actually Happening

When os/exec runs a command by name (not full path), it searches $PATH to find the binary. It searches its own $PATH β€” not yours.

Your terminal session carries a rich PATH like /usr/local/bin:/usr/bin:/home/user/.local/bin:/usr/local/go/bin. A systemd service, cron job, or Docker container starts with almost nothing β€” often just /usr/bin:/bin.

So gcc at /usr/local/bin/gcc, or go at /usr/local/go/bin/go, simply doesn't exist as far as your process is concerned.

Step 1: Confirm the Binary Exists

Rule out the obvious first β€” is the binary actually installed?

which gcc
# or
type gcc

# If not found:
sudo apt install gcc          # Debian/Ubuntu
sudo yum install gcc          # RHEL/CentOS
brew install gcc              # macOS

which returning a path means the binary exists. You have a PATH visibility problem, not an installation problem.

Step 2: Debug What Your Go Process Sees

Drop this snippet into your program to see exactly what $PATH looks like at runtime:

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    // Print the PATH your process actually has
    fmt.Println("PATH:", os.Getenv("PATH"))

    // Try to resolve the binary
    path, err := exec.LookPath("gcc")
    if err != nil {
        fmt.Println("LookPath error:", err)
    } else {
        fmt.Println("Found gcc at:", path)
    }
}

Run it under the same conditions where the error occurs β€” as the service user, inside Docker, or via sudo -u myuser ./binary. The truncated PATH will make the problem obvious.

Quick Fix: Use the Full Binary Path

Bypass PATH lookup entirely. Pass the absolute path to exec.Command:

cmd := exec.Command("/usr/bin/gcc", "-o", "output", "main.c")
out, err := cmd.CombinedOutput()
if err != nil {
    log.Fatalf("gcc failed: %v\n%s", err, out)
}

Works regardless of $PATH. The catch: binaries live in different places on different machines β€” /usr/local/bin/gcc on Homebrew macOS, /usr/bin/gcc on Ubuntu. Hardcoded paths break on the next box.

Better Fix: Resolve Path at Startup, Fail Fast

Resolve the path once at startup and store it. A missing binary then crashes immediately with a readable error β€” not silently three goroutines deep:

package main

import (
    "fmt"
    "log"
    "os/exec"
)

var gccPath string

func init() {
    var err error
    gccPath, err = exec.LookPath("gcc")
    if err != nil {
        log.Fatalf("Required binary 'gcc' not found in PATH: %v", err)
    }
}

func compileFile(src, out string) error {
    cmd := exec.Command(gccPath, "-o", out, src)
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("gcc error: %w\n%s", err, output)
    }
    return nil
}

The program either starts clean or dies immediately with a message that tells you exactly what's wrong. No surprises at 2am.

Permanent Fix: Pass the Right PATH to the Child Process

Hardcoded paths too brittle? Extend the subprocess's PATH directly in code. Handy when tools are scattered across /usr/local/bin, ~/.local/bin, or language-specific directories like /usr/local/go/bin:

package main

import (
    "os"
    "os/exec"
    "strings"
)

func runWithExtendedPath(name string, args ...string) ([]byte, error) {
    cmd := exec.Command(name, args...)

    // Inherit current env, but extend PATH
    env := os.Environ()
    for i, e := range env {
        if strings.HasPrefix(e, "PATH=") {
            env[i] = e + ":/usr/local/bin:/usr/local/go/bin:/home/ubuntu/.local/bin"
            break
        }
    }
    cmd.Env = env

    return cmd.CombinedOutput()
}

Or set the complete environment explicitly when you know exactly what's needed:

cmd := exec.Command("gcc", "-o", "output", "main.c")
cmd.Env = append(os.Environ(),
    "PATH=/usr/local/bin:/usr/bin:/bin",
)
out, err := cmd.CombinedOutput()

Fix for systemd Services

Systemd services start with a minimal environment β€” your user's PATH doesn't carry over. Set it explicitly in the unit file:

[Service]
Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/go/bin"
ExecStart=/opt/myapp/myapp

Prefer keeping variables in a separate file? Use EnvironmentFile=/etc/myapp/env instead. Either way, reload after editing:

sudo systemctl daemon-reload
sudo systemctl restart myapp

Fix for Docker Containers

Docker images start fresh β€” the binary must be installed and reachable from PATH. Verify both in your Dockerfile:

FROM golang:1.22-alpine

RUN apk add --no-cache gcc musl-dev

# Verify gcc is reachable before the build
RUN which gcc

COPY . /app
WORKDIR /app
RUN go build -o server .
CMD ["/app/server"]

Verify the Fix

Test under the exact conditions where the error was happening:

# For a service:
sudo systemctl restart myapp
journalctl -u myapp -n 50

# For Docker:
docker build -t myapp . && docker run myapp

# Quick sanity check in code:
path, err := exec.LookPath("gcc")
fmt.Printf("gcc resolved to: %s (err: %v)\n", path, err)

No error, subprocess exits cleanly? Ship it.

Summary

  • The error means os/exec can't find the binary in the process's $PATH β€” not that the binary doesn't exist.
  • Services, containers, and cron jobs don't inherit your interactive shell's PATH.
  • Use exec.LookPath to debug exactly what your Go process sees at runtime.
  • Quick fix: pass the absolute binary path to exec.Command.
  • Cleaner fix: resolve at startup with LookPath, crash immediately with a clear error.
  • For services: set Environment= in the systemd unit file.

Related Error Notes