Home › Rust › Rust Linking With cc Failed — Fix Fast
Intermediate 6 min · September 23, 2026

Rust Linking With cc Failed — Fix Fast

Fix Rust linking with cc failed: install the C toolchain, add -dev libraries, repair pkg-config paths, and set cross linkers..

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 9 min
  • ✓Rust installed via rustup with cargo available
  • ✓A crate with a -sys dependency (e.g. reqwest) to reproduce
  • ✓Linux (or macOS) shell with sudo or Docker access
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • linking with cc failed means rustc compiled fine but the system C linker couldn't finish — don't reinstall Rust
  • No cc in PATH is the commonest cause on fresh machines: install build-essential (Linux) or Xcode CLT (macOS)
  • -sys crates need system headers: libssl-dev / libpq-dev, or point with OPENSSL_DIR, or use the vendored feature
  • Read the first undefined reference line — those symbol lines are the real error, the cc summary is just the messenger
  • Cross builds need the target's linker pinned in .cargo/config.toml, not the host cc
✦ Definition~90s read
What is Rust Linking cc Failed Fix?

The error error: linking with cc failed: exit status: 1 is cargo's report that the final link step — merging compiled objects and system libraries into a binary — failed. Rustc finished compiling all Rust code successfully; then cargo invoked the platform C linker driver (cc, usually gcc or clang behind the name) with a long command of objects (-L search paths, -l libraries), and that command exited nonzero.

★
Think of building a Rust program as printing a book: rustc writes every chapter, then a bindery (the cc linker) stitches chapters plus C libraries like OpenSSL into one volume.

The failure lives entirely in the environment-linker layer: missing toolchain, missing system libraries, unfindable pkg-config paths, or a wrong linker for the target. Your Rust source is almost never the cause.

Five layers produce it. An absent C toolchain means cc doesn't exist. Missing -dev headers starve -sys crates (openssl-sys, pq-sys) whose build scripts probe for them. Broken pkg-config setup hides installed libraries from probes. Any of these surfaces as hundreds of undefined reference lines — the true error naming the missing provider per symbol prefix.

And cross-compilation adds linker mismatch: the host cc cannot emit the target's architecture without a dedicated cross linker pinned in .cargo/config.toml.

The fix is always environmental and layered in the same order: prove cc exists, satisfy -sys headers (or vendor), repair pkg-config paths, read the first undefined symbol as the diagnosis, and pin cross linkers per target. Teams that learn the layered read resolve these failures in minutes; teams that reinstall Rust chase a compiler that was never broken.

Plain-English First

Think of building a Rust program as printing a book: rustc writes every chapter, then a bindery (the cc linker) stitches chapters plus C libraries like OpenSSL into one volume. Linking with cc failed means the bindery couldn't work — its stitching machine is missing (no toolchain), an appendix never arrived (no dev headers), its catalog lost the address (pkg-config), or you ordered a binding it can't do (wrong cross linker). The chapters are fine; fix the bindery.

You run cargo build on a fresh machine and instead of a binary you get an essay ending in error: linking with cc failed: exit status: 1. Above it, hundreds of lines about undefined references, missing libraries, or a cc that was never found. Your code compiled — rustc did its job — and then the final step died.

That final step is the system linker, invoked as cc, stitching your compiled Rust plus any C code (from -sys crates like openssl-sys or pq-sys) into an executable. Rust doesn't ship this toolchain; it borrows your system's. When the toolchain is absent, a library's headers are missing, pkg-config can't find paths, or a cross target needs a different linker, the link fails and cargo reports the messenger: cc.

The error is infamous because the output misdirects. The last line names cc, so teams reinstall Rust — which changes nothing, since rustc was never broken. The real diagnosis sits lines above: cannot find -lssl, undefined reference to PQconnectdb, or simply cc: command not found. Each points at a different layer with a different fix.

This guide teaches the layered read: confirming the toolchain, satisfying -sys crates, fixing pkg-config, handling the undefined-symbol lines as the true error, and configuring cross linkers. You'll stop reinstalling Rust and start fixing environments.

Cargo splits building into two phases owned by two tools: rustc compiles every Rust crate (plus build-script output) into object files, then the platform linker — invoked as cc — merges those objects with system C libraries into your binary. Pure-Rust crates often link quietly with the default driver, so developers can use Rust for months without knowing cc exists. The moment a dependency includes C code — openssl-sys, pq-sys, sqlite's bundled build, ring's assembly — cc becomes load-bearing.

The error text mirrors the split: error: linking with cc failed: exit status: 1 names the failing phase and the exit code, while the cause hides in the invoked-command dump above (the exact cc command line cargo ran) and the undefined-reference or cannot-find lines it produced. Exit status 1 from ld-family linkers means unresolved inputs, not a crashed tool — the linker ran correctly and reported that pieces are missing.

Reproduce loudly before fixing: cargo build -vv prints the full cc invocation plus environment, and piping to a file preserves it (cargo build -vv 2>&1 | tee /tmp/build.log). That log is the incident artifact — it records compiler paths, library search dirs (-L flags), and linked libs (-l flags) exactly as cargo saw them. Every fix below starts from reading it.

Internalize the mental model: rustc errors are code bugs, cc errors are environment bugs. Reinstalling toolchains at random confuses the two; reading the log respects them.

BASH
1
2
3
4
5
6
7
8
9
# Capture the real evidence: full verbosity, saved to a file.
cargo build -vv 2>&1 | tee /tmp/build.log

# Triage the log: the summary is last, the cause is above.
grep -n "linking with" /tmp/build.log
grep -nE "cannot find -l|undefined reference|cc: error|pkg-config" /tmp/build.log | head -20

# Prove which layer is missing before installing anything.
which cc gcc pkg-config; cc --version | head -2
📊 Production Insight
Cache the failing cc command line in the incident ticket, not just the summary. Re-running that exact cc line standalone after installing a library proves the fix without waiting for a full rebuild.
🎯 Key Takeaway
rustc compiles, cc links — a cc failure with compiled crates in the log is an environment bug, so fix the system, not the code.

No cc in PATH: Install the Toolchain

The commonest cc failure on fresh machines is the simplest: no C toolchain installed. Rustup ships rustc, cargo, and standard libraries — not gcc, not glibc headers, not a linker driver. Minimal Docker images (rust:slim, alpine), new laptops, and bare CI runners all lack cc until someone installs it, and every crate with a build script or C dependency fails identically while pure-Rust crates sail through.

Install per platform and verify the same way everywhere: Debian/Ubuntu get sudo apt install build-essential (gcc, make, libc6-dev in one metapackage); Fedora/RHEL get Development Tools via dnf; macOS gets xcode-select --install for the Command Line Tools; Windows gets the MSVC Build Tools with the C++ workload. Then confirm with cc --version and gcc --version — both should print banners, since some build scripts probe gcc directly while cargo invokes cc.

Alpine and musl deserve a callout because they pair a different libc with extra needs: musl-tools plus the musl-gcc wrapper, and -sys crates often requiring vendored builds against musl headers. If your production image is Alpine but your dev machine is glibc Ubuntu, expect link differences and standardize on one libc for builds.

Bake the toolchain into every base: devcontainer Dockerfiles, CI base images, and onboarding scripts should install the compiler set before Rust code is ever fetched. A three-line install in the image saves every future hire the same lost afternoon.

BASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Debian/Ubuntu: the one metapackage that ends most fresh-machine failures.
sudo apt update && sudo apt install -y build-essential pkg-config
cc --version | head -1 && gcc --version | head -1

# macOS: Command Line Tools provide cc/clang + SDK headers.
# xcode-select --install

# Fedora/RHEL family:
# sudo dnf groupinstall -y 'Development Tools' && sudo dnf install -y pkg-config

# Slim Docker builder stage: never assume cc exists.
# FROM rust:1.76 AS builder
# RUN apt-get update && apt-get install -y gcc pkg-config libssl-dev
📊 Production Insight
Onboarding docs that start with 'install Rust' and stop are link-failure factories. The working sequence is toolchain first, rustup second, cargo build third — in that order, verified each step.
🎯 Key Takeaway
Rustup doesn't ship cc — install build-essential or Xcode CLT on every machine and image, then verify with cc --version.

-sys Crates Need -dev Headers

With a toolchain present, the next layer is -sys crates: Rust wrappers around system C libraries (openssl-sys for TLS, pq-sys for Postgres, libsqlite3-sys, libz-sys). Their build scripts probe for headers and libraries at compile time, and the link step needs the actual binaries. Installing only the runtime package (libssl3) leaves headers (ssl.h) and pkg-config files (*.pc) absent — the classic 'library installed but build fails' paradox.

Fix it at the right layer with three options in order. First, install the development package: libssl-dev on Debian/Ubuntu, openssl-devel on Fedora, openssl plus postgres libpq via Homebrew on macOS. Second, point the probe explicitly when libraries live in odd prefixes: OPENSSL_DIR, OPENSSL_LIB_DIR plus OPENSSL_INCLUDE_DIR, or PQ_LIB_DIR for Postgres. Third, sidestep the system entirely with the crate's vendored feature (openssl with vendored, or bundled SQLite), which compiles C from source — slower builds, zero system drift.

After changing anything, force the probe to rerun: cargo clean -p openssl-sys (or the failing -sys crate) then cargo build. Build scripts cache aggressively, so installing headers without cleaning replays the stale failure and convinces teams the fix didn't work. Watch the fresh probe output in -vv logs to confirm it found the new paths.

Record system deps as code: a README section, a build-deps script, or apt lines in the Dockerfile beside Cargo.toml. Unrecorded deps are future incidents wearing invisibility cloaks.

src/main.rsRUST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Cargo.toml: vendored OpenSSL dodges system drift entirely.
// [dependencies]
// reqwest = { version = "0.12", features = ["rustls-tls"] }
// # ...or keep native-tls with vendored OpenSSL:
// openssl = { version = "0.10", features = ["vendored"] }

fn main() {
    // Proves TLS works without touching system OpenSSL at link time.
    let body = reqwest::blocking::get("https://example.com")
        .expect("request failed")
        .text()
        .expect("read failed");
    println!("fetched {} bytes", body.len());
}
📊 Production Insight
Production servers need runtime libs while builders need dev headers — splitting them across image stages keeps prod lean without breaking builds, and documents which layer owns what.
🎯 Key Takeaway
Runtime packages don't build — install -dev headers, point probes with *_DIR vars, or vendor; then clean the -sys crate and rebuild.

Undefined Symbols Are the Real Error

Undefined-reference output terrifies because of volume: hundreds of lines like undefined reference to SSL_new' or PQconnectdb' scrolling past. But volume is an illusion — one missing library produces one error per symbol it should have provided. The diagnosis is always the first undefined line: it names the missing provider, and everything below is the same absence repeated per symbol.

Map symbol to library with pattern recognition. SSL_, TLS_, BIO_, EVP_ mean OpenSSL (libssl/libcrypto). PQ means Postgres client (libpq). sqlite3_ means SQLite. z_, inflate, deflate mean zlib. mysql_ mean the MySQL client. When unsure, ask the system: nm -D /usr/lib/x86_64-linux-gnu/libssl.so | grep SSL_new confirms a library provides the symbol, and ldd on a working binary shows which provider a healthy link chose.

Two subtleties trap experienced developers. Static-versus-shared mismatches: a -sys crate expecting static libs while only .so files exist (or the reverse behind musl) fails with undefined lines despite files being present — check for the right archive flavor. Versioned symbols: a crate needing OpenSSL 3 APIs against OpenSSL 1.1 headers fails on the new symbols only — openssl version versus the crate's documented minimum resolves it in one comparison.

Treat the symbol lines as the real error and the cc summary as its messenger. Fix the provider, clean the -sys crate, rebuild, and confirm the first symbol resolves — the cascade follows.

src/main.rsRUST
1
2
3
4
5
6
7
8
9
10
// src/main.rs: minimal repro that links OpenSSL through openssl-sys.
// [dependencies] openssl = "0.10"
use openssl::ssl::{SslConnector, SslMethod};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Undefined `SSL_new`-family errors here mean libssl didn't link.
    let _connector = SslConnector::builder(SslMethod::tls())?.build();
    println!("openssl linked: TLS connector built");
    Ok(())
}
⚠ One Library, Hundreds of Errors
Hundreds of undefined references look like total breakage but usually trace to one missing library. Fix the first symbol's provider, rebuild, and watch nine-tenths of the errors evaporate — never try to fix them line by line.
📊 Production Insight
Symbol-prefix fluency is the whole skill: SSL/PQ/sqlite3/z_ cover nearly every production case. New hires who learn five prefixes triage faster than veterans who reread full logs.
🎯 Key Takeaway
The first undefined reference names the missing library; map its prefix, verify with nm, and the cascade resolves as one fix.

pkg-config: Repair the Probe Path

pkg-config is the phone book between build scripts and system libraries: given a name like openssl, it returns the -I include paths and -L/-l link flags from .pc files. When the phone book is missing (pkg-config uninstalled in slim images), out of date, or searching the wrong directories (Homebrew's prefix isn't in the default path), probes fail with library-not-found errors while the library sits installed and innocent.

Diagnose the probe standalone, outside cargo: pkg-config --libs --cflags openssl should print flags; an empty result or Package openssl was not found error reproduces the build failure in one line. Then locate the real .pc file (find /usr /opt/homebrew -name 'openssl.pc') and compare its directory against echo $PKG_CONFIG_PATH. A mismatch is the whole bug — export the corrected path and the probe passes.

macOS Homebrew is the canonical case: brew install openssl puts .pc files under $(brew --prefix openssl)/lib/pkgconfig, which no default PKG_CONFIG_PATH includes. The permanent fix is exporting that path in the shell profile, the project's .envrc, and the CI image together — one layer fixed while others stay broken is how the error survives 'fixes'.

Some crates bypass pkg-config via explicit *_DIR variables (OPENSSL_DIR) or vcpkg on Windows. Those are valid escapes when .pc files are genuinely absent, but prefer repairing the phone book first — DIR overrides hardcode machine-specific paths that rot, while a correct pkg-config setup travels.

BASH
1
2
3
4
5
6
7
8
# Reproduce the probe outside cargo, then fix the search path.
 pkg-config --libs --cflags openssl || echo "PROBE FAILED"
 find /usr /opt/homebrew -name 'openssl.pc' 2>/dev/null
 echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH"

 # Homebrew macOS permanent fix (shell profile + CI image alike):
 export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig:$PKG_CONFIG_PATH"
 pkg-config --libs --cflags openssl  # expect: -L... -lssl -lcrypto
📊 Production Insight
CI-only pkg-config failures with green laptops mean the image lacks what Homebrew provided locally. Diff installed packages between laptop and image before touching crate config.
🎯 Key Takeaway
Reproduce the probe with bare pkg-config, align PKG_CONFIG_PATH with the real .pc location, and fix all shells and images together.

Cross Builds Need Their Own Linker

Cross-compilation fails at link whenever the host cc is asked to emit a foreign binary: building on x86_64 for aarch64 with the default linker produces architecture-mismatch or linker-not-found lines after a flawless compile. Rust's rustc handles codegen per target, but the final link needs a cross-capable linker plus target libraries — pieces rustup doesn't install.

Assemble the three parts. First, the std component: rustup target add aarch64-unknown-linux-gnu (or x86_64-unknown-linux-musl for static builds). Second, the linker binary: gcc-aarch64-linux-gnu on Debian/Ubuntu, musl-tools plus musl-gcc for musl targets, or the appropriate LLVM mingw for Windows targets. Third, the pin: a [target.aarch64-unknown-linux-gnu] section in .cargo/config.toml setting linker = "aarch64-linux-gnu-gcc", checked into the repo so every machine and CI runner agrees.

Then chase host-path leakage, the chronic follow-up failure. Build scripts and pkg-config probes run on the host and happily return host include/lib paths that the cross linker can't use. Scope PKG_CONFIG_PATH (or _DIR overrides) per target with TARGET_ prefixed env vars, or run the build inside the documented cross image (rustembedded/cross or a custom builder) where host and target sysroots are separated by construction.

Verify deliberately: file target/debug/app reports the target architecture, and ldd (or its musl/static equivalent) shows the expected linkage. A binary that links but targets the wrong arch is a runtime mystery — check the artifact, not just the exit code.

BASH
1
2
3
4
5
6
7
8
9
# .cargo/config.toml: pin the cross linker so every machine agrees.
# [target.aarch64-unknown-linux-gnu]
# linker = "aarch64-linux-gnu-gcc"

# Assemble all three parts, then build:
rustup target add aarch64-unknown-linux-gnu
sudo apt install -y gcc-aarch64-linux-gnu
cargo build --target aarch64-unknown-linux-gnu
file target/aarch64-unknown-linux-gnu/debug/app  # expect: ARM aarch64
📊 Production Insight
Standardize cross builds on one maintained image (cross-rs or a pinned custom builder) rather than per-developer linker installs. Version drift between hand-installed cross toolchains is its own incident category.
🎯 Key Takeaway
Cross links need target std, a cross linker, and a config.toml pin — plus TARGET_-scoped paths so host libraries can't leak in.
● Production incidentPOST-MORTEMseverity: high

A Slim Image Bump Dropped the C Toolchain and Broke Every Build

Symptom
Overnight, all CI builds for an API service failed with error: linking with cc failed after a routine base-image bump. No Rust source had changed. Pure-Rust unit tests compiled, but the final binary — which links OpenSSL via reqwest — died with cannot find lines. The team burned an hour on Rust-version theories before reading the Dockerfile diff.
Assumption
The team blamed the Rust upgrade. The base-image bump had moved rust:1.75-slim to rust:1.76-slim, so the incident review opened with toolchain-version theories and a proposed Rust downgrade. Two engineers spent an hour comparing rustc changelogs while the actual diff — the slim image never contained gcc, and the old image had received it transitively via a since-removed layer — sat in the Dockerfile history unexamined.
Root cause
The slim variant never guaranteed a C toolchain — the previous image had carried gcc transitively through a layer the maintainers later removed. Every crate with C code (openssl-sys via reqwest) failed at the cc link step with cannot find -lc and missing-header lines, while pure-Rust crates built fine. The Rust version was irrelevant; the environment had lost gcc, pkg-config, and libssl-dev in one image bump.
Fix
The hotfix was apt install -y gcc pkg-config libssl-dev in the builder stage, restoring green builds in minutes. The durable fix: a dedicated builder stage FROM rust:1.76 AS builder with the three packages installed explicitly, a CI step asserting cc --version before cargo build, and the system dependency list checked into the repo beside Cargo.toml so image bumps can't silently drop it again.
Key lesson
  • Slim images are guilty until proven innocent — assert cc --version in CI before any build, not after a failure.
  • Blame the environment diff, not the compiler version — Dockerfile history answers faster than changelogs.
  • Check system deps into the repo — a packages list beside Cargo.toml survives image bumps that tribal knowledge doesn't.
Production debug guideCapture the full log first, then let the first missing piece name the layer.5 entries
Symptom · 01
cc not found, or fresh machine fails every non-trivial crate
→
Fix
Run which cc gcc pkg-config and cc --version. Expect a path and a version banner. If cc is missing, install it: sudo apt install build-essential on Debian/Ubuntu, sudo dnf groupinstall 'Development Tools' on Fedora/RHEL, xcode-select --install on macOS. In slim Docker images add gcc explicitly (apt install -y gcc pkg-config). Then run cargo build again before touching anything else.
Symptom · 02
cannot find -lssl or openssl-sys/pq-sys build script failure
→
Fix
Run cargo build -vv 2>&1 | tee /tmp/build.log, then grep -iE 'cannot find -l|undefined reference|pkg-config|failed to run' /tmp/build.log. Expect one cluster naming the missing piece (e.g. cannot find -lssl). Install the matching dev package: sudo apt install libssl-dev libpq-dev, or brew install openssl pq. Then cargo clean -p openssl-sys (the failing -sys crate) and rebuild so its probe reruns.
Symptom · 03
Build says the library is missing although files exist on disk
→
Fix
Run pkg-config --libs --cflags openssl (expect -L/-I flags) and echo $PKG_CONFIG_PATH. On Homebrew macOS run brew --prefix openssl and export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig:$PKG_CONFIG_PATH". Where pkg-config itself is absent (minimal images), install it first. Re-run the bare pkg-config command until it prints flags, then cargo build.
Symptom · 04
Pages of undefined reference lines bury the cause
→
Fix
In /tmp/build.log find the first undefined reference to ... line — that symbol is the true error; everything after is fallout. Map it: PQ means libpq, SSL_ means OpenSSL, z_* means zlib. Confirm with nm -D /usr/lib/x86_64-linux-gnu/libpq.so | grep PQconnectdb (expect the symbol listed) or ldd on the built rlib. Install or point at the providing library, clean the -sys crate, rebuild.
Symptom · 05
Host builds pass, cross target fails at link
→
Fix
Run rustup target list --installed and confirm the target's std is present; rustup target add aarch64-unknown-linux-gnu if not. Install the linker: sudo apt install gcc-aarch64-linux-gnu, then pin it in .cargo/config.toml under [target.aarch64-unknown-linux-gnu] as linker = "aarch64-linux-gnu-gcc". Rebuild with cargo build --target aarch64-unknown-linux-gnu and read any remaining error as host-vs-target path leakage.
Rust cc-Link Failures — Confirm and Fix Each
Root CauseHow to ConfirmFixPrevention
Missing C toolchain (no cc in PATH)which cc empty; error shows unable to find tool or no such fileInstall build-essential or Xcode CLT, then verify cc --versionBake the toolchain into dev images and CI base images
Missing system library behind a -sys crateundefined reference to ssl/crypto/pq-like symbols; pkg-config finds nothingInstall the -dev package (libssl-dev) or set OPENSSL_DIR/vendoredDocument system deps per crate; add a build-deps CI job
pkg-config can't locate the .pc filecargo build -vv shows pkg-config probe failure with path hintsExport PKG_CONFIG_PATH to the .pc dir or install pkg-config itselfSet PKG_CONFIG_PATH in the project's env file and containers
Wrong linker for the target (cross builds)error mentions unknown linker or incompatible architecture tripleInstall the target linker (gcc-aarch64-linux-gnu) and set target linkerKeep a .cargo/config.toml per target with linker + image
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
cargo build -vv 2>&1 | tee /tmp/build.logTwo Phases
sudo apt update && sudo apt install -y build-essential pkg-configNo cc in PATH
srcmain.rsfn main() {-sys Crates Need -dev Headers
srcmain.rsuse openssl::ssl::{SslConnector, SslMethod};Undefined Symbols Are the Real Error
pkg-config --libs --cflags openssl || echo "PROBE FAILED"pkg-config
rustup target add aarch64-unknown-linux-gnuCross Builds Need Their Own Linker

Key takeaways

1
cc failed means the environment failed
rustc compiled fine, so don't reinstall Rust, inspect the system instead.
2
Read bottom-up
the last cc line is the summary, the undefined symbols above are the real error.
3
-sys crates need -dev headers plus pkg-config paths
runtime libraries alone never satisfy a build.
4
Slim images and new laptops lack toolchains
bake build-essential or Xcode CLT into every base.
5
Cross targets need their own linker pinned in .cargo/config.toml
the host cc can't emit foreign binaries.
6
Reproduce with cargo build -vv and probe standalone
captured commands beat guessed fixes.

Common mistakes to avoid

5 patterns
×

Reading the panic instead of the linker line

Symptom
Hours spent on the final error: linker command failed while the actual missing symbol or missing library sits fifty lines up, unnamed in the summary.
Fix
Read the last cc invocation and the first undefined reference before scrolling. The real error is a handful of lines; the thousand-line dump is context. Pipe build output to a file and grep for error and undefined.
×

Assuming Rust needs no C toolchain

Symptom
Fresh container or new laptop fails every crate with a build script or C dependency, while pure-Rust crates compile fine — cc is simply absent.
Fix
Install build-essential (or Xcode CLT on macOS), confirm cc --version prints, then rebuild. In slim Docker images add the gcc package explicitly — rust images don't bundle a C toolchain.
×

Installing the runtime library but not the dev headers

Symptom
libssl exists so the app would run, but openssl-sys fails — headers and .pc files ship in the -dev package the build needs and production doesn't.
Fix
Install the matching -dev package (libssl-dev, libpq-dev), or point the crate with OPENSSL_DIR / PQ_LIB_DIR, or enable the vendored feature to compile from source. Then cargo clean -p the -sys crate and rebuild.
×

Ignoring pkg-config failures and PATH

Symptom
The library is installed yet the build claims otherwise — the probe searched default paths while Homebrew or a custom prefix put .pc files elsewhere.
Fix
Run cargo build -vv to see the exact probe, then export PKG_CONFIG_PATH to the directory holding the .pc file (or install pkg-config where missing). Re-run the probe standalone with pkg-config --libs --cflags to confirm.
×

Cross-compiling with the host linker

Symptom
Host builds pass but the ARM or musl target fails with linker-not-found or architecture-mismatch lines — cc resolves to the host toolchain, which can't emit the target.
Fix
Install the target's linker package, declare it under [target.triple] in .cargo/config.toml, and cross-check with rustup target list that the std component matches. Build in the documented cross image rather than a generic one.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does linking with cc failed actually mean?
Q02JUNIOR
Why does Rust need a C linker at all?
Q03SENIOR
openssl-sys fails although libssl is installed. What's missing?
Q04SENIOR
How do you debug a pkg-config probe failure?
Q05SENIOR
Cross-compiling to ARM fails at the link step. Walk through your fix.
Q01 of 05JUNIOR

What does linking with cc failed actually mean?

ANSWER
It means rustc compiled successfully but the system linker (cc) couldn't produce the binary — missing toolchain, missing system library, or bad linker config. I'd read the undefined-reference or missing-library lines above the summary, since they name the actual absent piece.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Does this error mean my Rust code is wrong?
02
What extra setup do musl targets need?
03
Is enabling a crate's vendored feature safe?
04
Should I delete Cargo.lock to fix version conflicts?
05
How long do these failures usually take to fix?
06
Why does Rust need a C compiler at all?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
🔥

That's Core. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Rust Borrow Checker Lifetimes Fix
6 / 6 · Core