This error happens before the linker and before TLS code runs: the compiler cannot locate the header named by #include <openssl/ssl.h>. On a normal native Ubuntu build, libssl-dev is the right package. If it is already installed, the interesting causes are usually a cross-compilation sysroot, a custom OpenSSL prefix, stale CMake cache, or build flags that discard the system include path.
Understand the package split
opensslprovides command-line utilities.The runtime package provides versioned shared libraries needed by already-built programs.
libssl-devprovides C headers, unversioned linker files, static archives where packaged, CMake configuration, and pkg-config metadata.libsslimplements TLS/DTLS and depends onlibcrypto; crypto-only programs may need only libcrypto.Installing development files fixes discovery, not source compatibility with a different OpenSSL major version.
1. Confirm the exact compiler failure
#include <openssl/opensslv.h>
#include <openssl/ssl.h>
#include <stdio.h>
int main(void)
{
printf("%s\n", OpenSSL_version(OPENSSL_VERSION));
return 0;
}The probe needs both headers and libcrypto symbols
Angle-bracket includes ask the compiler’s configured include search paths.
opensslv.hexposes compile-time version declarations/macros.ssl.hdeclares the TLS API.OpenSSL_versionis provided by libcrypto, so successful preprocessing alone is not the final test.This probe reports the linked library version at runtime; it does not create a secure TLS connection.
cc -std=c17 -Wall -Wextra -Wpedantic -c openssl_probe.cBefore development headers are available, compilation stops with fatal error: openssl/ssl.h: No such file or directory (or at opensslv.h first).Compile errors and link errors are different stages
-cstops after producing an object file and does not link libraries.A missing header is a preprocessing/compilation search-path problem.
An “undefined reference” later is a linker dependency/order problem.
A loader error such as missing
libssl.sois a runtime library search/ABI problem.Diagnose the first failing stage instead of reinstalling packages indiscriminately.
2. Inspect package state before installing
apt-cache policy libssl-dev
dpkg-query -W -f="${Status} ${Version}\n" libssl-dev 2>/dev/null || trueAPT shows the repository candidate; dpkg reports install status/version when present.Repository evidence prevents guesswork
apt-cache policyis read-only and reveals the selected repository version.dpkg-querychecks the local package database.The escaped format prints status and version;
|| trueis acceptable for this optional diagnostic, not for hiding build failures.Ubuntu releases and enabled security/updates pockets supply different patched versions.
Do not add random PPAs merely to obtain a header that the supported repository already provides.
3. Install the development package
sudo apt update
sudo apt install libssl-dev pkg-configAPT refreshes package indexes, then asks to install/upgrade the development files and pkg-config tooling with repository-matched dependencies.Risk level: caution. Review the command before running it.
Review the transaction before approving
Administrative commands change system packages.
libssl-devmust match the repository runtime ABI package selected by APT.pkg-configprovides build flags from installed.pcmetadata.Use pinned container/CI images for reproducible builds rather than modifying hosts during every job.
Security updates can change the patch version; rebuild/test native dependents according to release policy.
4. Verify the installed header and metadata
dpkg -L libssl-dev | grep -E "/openssl/(ssl|opensslv)\.h$"
pkg-config --modversion openssl
pkg-config --cflags --libs opensslExpect packaged header paths, an OpenSSL version, and link flags typically containing -lssl -lcrypto (include/library flags may be empty for default system paths).Empty cflags can be correct
dpkg -Lproves which installed package owns the files.On a native system install,
/usr/includeis already a compiler default, so pkg-config may emit no-Iflag.The
opensslpkg-config module represents both libraries for typical TLS consumers.Do not confuse
/usr/include/openssl/ssl.hwith NSS’s differently locatedssl.h.If metadata selects
/usr/local, a custom installation may be shadowing Ubuntu packages.
5. Compile and link with pkg-config
cc -std=c17 -Wall -Wextra -Wpedantic \
-o openssl_probe openssl_probe.c \
$(pkg-config --cflags --libs openssl)
./openssl_probeThe program should print the runtime OpenSSL version selected by the loader.Let metadata carry platform paths
Command substitution injects flags reported by the trusted local pkg-config database.
Source/object files appear before
-lssl -lcrypto, which matters for one-pass/static linkers.libssldepends onlibcrypto; the metadata preserves the expected relationship/order.Do not pass untrusted
PKG_CONFIG_PATHor shell content into a privileged build.For production, record compiler, flags, pkg-config version, linked artifacts, and runtime loader resolution.
6. Use imported CMake targets
cmake_minimum_required(VERSION 3.20)
project(openssl_probe LANGUAGES C)
find_package(OpenSSL REQUIRED COMPONENTS SSL)
add_executable(openssl_probe openssl_probe.c)
target_compile_features(openssl_probe PRIVATE c_std_17)
target_link_libraries(openssl_probe PRIVATE OpenSSL::SSL)OpenSSL::SSL carries usage requirements
REQUIREDstops configuration with a clear failure instead of producing a broken target.Requesting component
SSLensures the TLS library is present.OpenSSL::SSLcarries include/link requirements and also linksOpenSSL::Cryptoas documented by CMake.Target-scoped dependencies avoid leaking flags globally.
Specify a supported version/range when the source requires a particular OpenSSL API.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --verbose
./build/openssl_probeCMake should report the found OpenSSL installation/version, compile the target, and the executable should print its runtime version.A stale cache can pin the wrong installation
-Sand-Bkeep generated files out of source.Verbose output reveals actual include directories and libraries.
If OpenSSL was moved/upgraded, use a fresh build directory rather than editing cache internals.
OPENSSL_ROOT_DIRis a hint for intentional custom installations; do not set it blindly.Cross builds need a toolchain/sysroot configuration, not host
/usrpaths.
If libssl-dev is installed but the header is still missing
Run the failing compile with verbose/preprocessor include tracing to see real search paths.
Check whether
-nostdinc, an isolated sysroot, container, chroot, snap, or remote build excludes host headers.Confirm the compiler architecture/target matches the installed development package.
Inspect
PKG_CONFIG_PATH,PKG_CONFIG_LIBDIR, CMake cache,OPENSSL_ROOT_DIR, and customCPATH/include flags.Look for
/usr/local/include/opensslor vendored headers shadowing the distro version.Ensure the build command runs in the same environment where the package was installed.
Cross-compilation requires target headers
Installing native amd64 libssl-dev does not make those headers/libraries correct for an ARM sysroot. Use target-architecture packages or build/install OpenSSL into the target sysroot, configure pkg-config/CMake to search only that sysroot, and prevent host libraries from leaking into the link.
Headers must match the target library ABI/configuration.
Multiarch package syntax and availability depend on enabled architectures/repositories.
Set compiler target, sysroot, pkg-config sysroot/library directories, and CMake toolchain coherently.
Never “fix” cross builds with a raw
-I/usr/includeor-L/usr/libhost escape.Run artifact inspection and target/emulator tests before release.
Container builds need a build stage
FROM ubuntu:24.04 AS build
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential libssl-dev pkg-config \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
COPY . .
RUN cc -std=c17 -O2 -o openssl_probe openssl_probe.c \
$(pkg-config --cflags --libs openssl)Keep compilers and headers out of the runtime image
Pin the base image by digest and use a trusted update/rebuild policy for reproducibility and security.
A multi-stage runtime should copy only the executable and require the compatible runtime library package.
Dynamic linkage means the final image still needs matching
libssl/libcryptoruntime libraries.Static linking changes licensing, update, provider/module, NSS/certificate, and vulnerability-patching responsibilities.
Do not bake credentials or private package tokens into image layers.
Header fixed, but undefined references remain
Add
-lssl -lcryptothrough pkg-config, or link CMakeOpenSSL::SSL.Place libraries after source/object files for linkers that resolve left to right.
Do not use only
-lcryptofor APIs implemented in libssl.With static archives, additional dependencies and group/order rules may be required; use metadata.
Confirm C and C++ compiler/link driver consistency and ABI/toolchain compatibility.
Header and library version mismatch
pkg-config --modversion openssl
ldd ./openssl_probe | grep -E "lib(ssl|crypto)"
./openssl_probeCompare the build metadata version, loader-selected shared objects, and program-reported runtime version.Compilation success can hide runtime selection
lddis appropriate for a trusted local binary; do not run it on untrusted executables because implementations may execute code.RPATH/RUNPATH,
LD_LIBRARY_PATH,/usr/local, containers, and loader cache influence selection.OpenSSL major versions can change/deprecate APIs and ABI names.
Remove unintended custom installations or configure an intentional isolated prefix consistently.
Never copy random
.sofiles into system directories to silence a loader error.
OpenSSL 3 migration is a separate task
Installing current headers may expose deprecated low-level algorithms/APIs in old source.
Prefer documented high-level EVP interfaces for cryptographic operations.
OpenSSL 3 providers replace many legacy engine/algorithm-loading assumptions.
The legacy provider is not a blanket production fix and does not make obsolete cryptography safe.
Read the migration guide, update code, test protocol/certificate/provider behavior, and define supported versions explicitly.
Common error map
openssl/ssl.h missing: install target-correct development files or fix include/sysroot discovery.
Package installed, compiler still fails: build runs in another container/sysroot/architecture or uses
-nostdinc/wrong flags.undefined reference to SSL_*: libssl is not linked or appears in the wrong order.
undefined reference to EVP/OPENSSL_*: libcrypto missing/order/version mismatch.
CMake could not find OpenSSL: stale cache, wrong prefix/toolchain/sysroot, or development package absent.
Wrong version found: custom
/usr/local, environment metadata, cache, or runtime loader overrides distro installation.Works locally, fails CI: CI image lacks
libssl-dev/pkg-config or uses a different Ubuntu/OpenSSL/toolchain version.Runtime cannot open libssl.so: runtime package/loader path/ABI differs from build environment.
Verification checklist
The first failing stage—preprocess, compile, link, load, or API migration—is identified.
libssl-devcomes from an approved repository and matches target architecture/runtime.Package file list and pkg-config/CMake identify the intended headers and libraries.
Build uses metadata/imported targets instead of hard-coded host paths.
Clean native and CI/container builds pass with warnings enabled.
Runtime loader selects the intended version and smoke/integration TLS tests pass.
OpenSSL security updates, supported major versions, provider/configuration, certificate trust, and rebuild ownership are documented.
Primary references
Ubuntu libssl-dev package documents the supported development package and dependencies for Ubuntu 24.04.
OpenSSL libraries introduction explains libssl, libcrypto, and providers.
CMake FindOpenSSL documents components, imported targets, version requests, and root hints.
OpenSSL documentation provides API references, guides, migration, TLS, and provider documentation.
Comments and corrections