SSL & Certs Intermediate

Resolving ‘OpenSSL self signed certificate in certificate chain’ Errors on Debian 12 Bookworm

Fix OpenSSL validation errors on Debian 12 Bookworm when connecting to services using self-signed or private CA certificates. Learn to manage trusted CAs.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Fix OpenSSL validation errors on Debian 12 Bookworm when connecting to services using self-signed or private CA certificates. Learn to manage trusted CAs.

This guide addresses a common SSL/TLS certificate validation error encountered on Debian 12 Bookworm systems: "OpenSSL self signed certificate in certificate chain." This issue typically arises when your system or an application running on it attempts to connect to a server presenting a certificate that is either self-signed, or issued by a custom/private Certificate Authority (CA) whose root certificate is not inherently trusted by the client's operating system. Resolving this involves explicitly teaching your Debian system to trust the issuer of the certificate.

Symptom & Error Signature

Users will typically encounter this issue when a client application (e.g., curl, wget, a Docker client, a PHP script, or apt for a custom repository) attempts to establish a secure connection to a server using a certificate not verifiable against the system's trusted CA store. The exact error message can vary depending on the application, but the core issue points to a failure in certificate chain validation.

Common Error Outputs:

1. curl:

$ curl https://your-untrusted-server.com/api/data
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

2. wget:

$ wget https://your-untrusted-server.com/file.txt
--2026-09-09 10:30:00--  https://your-untrusted-server.com/file.txt
Resolving your-untrusted-server.com (your-untrusted-server.com)... 192.0.2.10
Connecting to your-untrusted-server.com (your-untrusted-server.com)|192.0.2.10|:443... connected.
ERROR: cannot verify your-untrusted-server.com's certificate, issued by 'CN=Your Custom CA':
  Self-signed certificate in certificate chain.
To connect to your-untrusted-server.com insecurely, use '--no-check-certificate'.

3. PHP (e.g., Guzzle HTTP client):

// In PHP error logs or script output
PHP Warning:  stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: error:0A000086:SSL routines::certificate verify failed in /path/to/your/script.php on line X
PHP Warning:  stream_socket_client(): Failed to enable crypto in /path/to/your/script.php on line X
PHP Warning:  stream_socket_client(): unable to connect to ssl://your-untrusted-server.com:443 (Unknown error) in /path/to/your/script.php on line X

4. Docker (pulling from a private registry):

$ docker pull your-private-registry.com/myimage:latest
Error response from daemon: Get "https://your-private-registry.com/v2/": x509: certificate signed by unknown authority

Root Cause Analysis

The "self-signed certificate in certificate chain" error indicates a Public Key Infrastructure (PKI) trust issue. When a client initiates an SSL/TLS handshake, the server presents its digital certificate. The client then attempts to validate this certificate by tracing its chain of trust back to a trusted Root Certificate Authority (CA).

The underlying reasons for this specific error are typically one or more of the following:

  1. Untrusted Root CA: The server's certificate is issued by an Intermediate CA, which in turn is signed by a Root CA. This Root CA's certificate is not present in the client system's default bundle of trusted certificates (e.g., /etc/ssl/certs/ca-certificates.crt). This is common in enterprise environments using their own internal CAs or development environments with self-signed certificates.
  2. Self-Signed Intermediate Certificate: One of the certificates within the chain (an Intermediate CA certificate) is itself self-signed, and this specific self-signed certificate is not trusted by the client system.
  3. Missing Intermediate Certificates on Server: Although less common for the "self-signed in chain" error, sometimes the server fails to provide the full certificate chain during the TLS handshake. If the client doesn't have the intermediate CA certificate(s) in its local trust store, it cannot complete the chain validation back to a trusted root.
  4. Application-Specific Trust Stores: Some applications or programming languages (e.g., Java, Python, PHP) might maintain their own independent trust stores, or have configurations that override the system-wide trust store. If the custom CA is added only to the system trust store but not to an application's specific configuration, the error will persist for that application.
  5. Expired or Invalid Certificates (Less Likely for this Specific Error): While general certificate problems can lead to validation failures, the "self-signed in certificate chain" message specifically points to a trust path issue rather than an expiry or malformation issue of the leaf certificate itself.

Step-by-Step Resolution

The primary solution involves adding the custom Root CA certificate (or the self-signed intermediate CA certificate) to your Debian 12 system's trusted certificate store.

1. Obtain the CA Certificate(s)

You need the public certificate file(s) for the Root CA (and potentially any Intermediate CAs) that issued the server's certificate. These files are typically in .crt or .pem format.

  • From your CA Administrator: This is the most reliable method. They should provide you with the necessary .crt or .pem files.

  • Extracting from the server (if accessible): You can use openssl s_client to connect to the server and extract the certificate chain.

    # Replace your-untrusted-server.com with the actual hostname
    # The -showcerts option displays the entire certificate chain
    # The output from '-----BEGIN CERTIFICATE-----' to '-----END CERTIFICATE-----'
    # needs to be copied for each certificate in the chain, starting from the Root CA.
    openssl s_client -showcerts -connect your-untrusted-server.com:443 </dev/null
    

    When extracting certificates from the server, you will see multiple -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- blocks. The last certificate in the output is usually the Root CA (or the self-signed intermediate), which is what you need to trust. Copy only that block (or the relevant Root/Intermediate CA certificates) into a new file, say custom_ca.crt.

2. Place the CA Certificate(s) in the System Trust Store

Once you have the CA certificate file(s) (e.g., custom_ca.crt), you need to place them in the appropriate directory for Debian to recognize them.

# Create a directory for custom CA certificates if it doesn't exist
sudo mkdir -p /usr/local/share/ca-certificates/extra

# Copy your CA certificate file into this directory
# Replace custom_ca.crt with the actual filename of your CA certificate
sudo cp custom_ca.crt /usr/local/share/ca-certificates/extra/custom_ca.crt

# Ensure correct permissions (readable by all)
sudo chmod 644 /usr/local/share/ca-certificates/extra/custom_ca.crt

3. Update the System's CA Certificate Bundle

After placing the new CA certificate, you must instruct Debian to regenerate its system-wide trusted CA bundle.

sudo update-ca-certificates

You should see output similar to this, indicating your custom CA was added:

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

The update-ca-certificates command creates/updates /etc/ssl/certs/ca-certificates.crt, which is a concatenated file containing all trusted certificates, and creates symlinks in /etc/ssl/certs/ to individual .crt files. Most applications configured to use the system trust store will rely on these.

4. Verify the Trust

Now, re-run the curl or wget command that previously failed. It should now succeed.

$ curl https://your-untrusted-server.com/api/data
# Expected output: the data from your server, no SSL error.

You can also use openssl verify to explicitly check your certificate against the system's trust store:

# If you have the server's leaf certificate (server.crt)
# This command verifies server.crt using the system's default CA path
openssl verify server.crt
# If it fails, specify the CA bundle directly
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.crt
# If it succeeds, the output will be "server.crt: OK"

5. Application-Specific Considerations

While update-ca-certificates typically handles most system-level applications, some specific services might require additional steps.

5.1. Docker Daemon

If you are pulling images from a private Docker registry that uses your custom CA:

# Create the directory for the registry's certificates
# Replace your-private-registry.com with your registry's hostname and port
sudo mkdir -p /etc/docker/certs.d/your-private-registry.com:443

# Copy your CA certificate into this directory, naming it ca.crt
sudo cp custom_ca.crt /etc/docker/certs.d/your-private-registry.com:443/ca.crt

# Restart the Docker daemon for changes to take effect
sudo systemctl restart docker
5.2. PHP Applications

Many PHP applications rely on curl or stream_context_create functions. If curl is used, the system-wide update should resolve it. For stream_context_create, PHP typically looks for openssl.cafile or openssl.capath in php.ini.

# Edit your php.ini file (e.g., for php-fpm or CLI)
# Find the relevant php.ini path:
# php --ini (for CLI)
# php-fpm -i | grep 'Loaded Configuration File' (for FPM)

sudo nano /etc/php/8.2/cli/php.ini # Example path

# Ensure these lines are commented out or set correctly:
;openssl.cafile=
;openssl.capath=

# If you must explicitly set it, point it to the system bundle:
openssl.cafile=/etc/ssl/certs/ca-certificates.crt

If you've explicitly configured openssl.cafile or openssl.capath to point to an outdated or incorrect CA file, ensure it's removed or updated to point to the system's ca-certificates.crt. Remember to restart your PHP-FPM service if you modified its php.ini.

sudo systemctl restart php8.2-fpm # Adjust version as needed
5.3. APT for Custom Repositories

If you're using apt with a custom repository that's served over HTTPS using your private CA, the system-wide update-ca-certificates should be sufficient. If you encounter issues like The certificate is NOT trusted., double-check the CA installation.

6. Troubleshooting (If the issue persists)

  • Verify Certificate Chain: Ensure you've added the correct certificate(s) to the trust store. If the server sends an intermediate certificate, make sure its issuer's certificate (the root) is trusted.
    # Inspect your custom CA certificate details
    openssl x509 -in /usr/local/share/ca-certificates/extra/custom_ca.crt -text -noout | grep "Issuer:"
    openssl x509 -in /usr/local/share/ca-certificates/extra/custom_ca.crt -text -noout | grep "Subject:"
    
    The Issuer of your server's certificate should match the Subject of your intermediate CA. The Issuer of your intermediate CA should match the Subject of your Root CA. If the Root CA is self-signed, its Issuer and Subject will be the same.
  • Check File Permissions: Ensure the .crt files in /usr/local/share/ca-certificates/ are readable by all users (e.g., chmod 644).
  • Restart Services: Always restart applications or services that make network requests after updating CA certificates, as they might cache the old trust store.
  • Debug with SSL_CERT_DIR: For curl or wget, you can temporarily point to a specific directory to test:
    # Create a temporary directory and put your CA cert there
    mkdir /tmp/test-certs && cp custom_ca.crt /tmp/test-certs/
    
    # Use curl with the temporary certs
    SSL_CERT_DIR=/tmp/test-certs curl https://your-untrusted-server.com/
    
    If this works, it indicates the issue is with the system-wide update or an application's specific configuration.

While it's possible to bypass SSL/TLS certificate validation (e.g., curl -k, wget --no-check-certificate), this is highly discouraged in production environments. Disabling validation removes a critical security layer, making your connections vulnerable to man-in-the-middle attacks. Always aim to properly configure trusted CAs.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.