Your site loads perfectly in Chrome, the padlock's green, nobody's complaining — and then you run a curl command on the same server and get slapped with SSL certificate problem: unable to get local issuer certificate. Git clone fails the same way. Composer install dies mid-download. A cron job that's pulled data from an API for two years suddenly stops working. Nothing about your site's actual SSL certificate changed, so what's going on?

This one trips people up because it looks like a website SSL error but it isn't. It's a trust-store problem on the server itself, and it only shows up in command-line tools, not browsers. Here's why it happens and how to fix it properly on a SkyServer VPS.

Symptom

You'll see one of these, almost always from the terminal rather than a browser:

  • curl: (60) SSL certificate problem: unable to get local issuer certificate
  • fatal: unable to access '...': server certificate verification failed (git)
  • cURL error 60: SSL certificate problem: unable to get local issuer certificate (Composer, WordPress HTTP API, PHP scripts)
  • A cron job's log fills up with SSL handshake failures while the same URL opens fine in a browser

The tell-tale sign is the mismatch: browsers trust the site, but anything using the system's own CA store doesn't. That split is the whole clue.

Cause

Every TLS connection gets verified against a bundle of trusted root and intermediate certificate authorities. Browsers ship their own bundle and update it constantly through the browser vendor. Your Linux server, on the other hand, relies on a package — ca-certificates on Debian/Ubuntu, ca-certificates plus update-ca-trust on AlmaLinux/RHEL — and that package only updates when you patch the OS. If that bundle is stale, missing an intermediate CA, or was never installed correctly (common on minimal/container base images), any tool that relies on it — curl, git, wget, PHP's cURL extension, Python's requests, Composer — will refuse the connection even though the certificate chain is perfectly valid.

A few specific triggers we see constantly on VPS support tickets:

  • Stale OS image. The VPS was provisioned months ago and never had ca-certificates updated since, so a newer intermediate CA (issued after your image was built) isn't in the local store.
  • Minimal/Docker base images. Slim images like alpine or debian-slim often ship without ca-certificates installed at all.
  • PHP pointing at the wrong CA bundle. php.ini has a curl.cainfo or openssl.cafile directive pointing at a file that doesn't exist or is outdated, so PHP ignores the correct system store entirely.
  • A private CA or self-signed cert somewhere in the chain that was never added to the trust store — common with internal APIs or dev/staging services.
  • Clock drift. Rarely the actual error message, but a VPS with a wrong system clock can also fail chain validation. Worth ruling out.

Fix

1. Update the CA certificate bundle

This resolves the vast majority of cases. SSH into the VPS and run the command for your distro:

# Debian / Ubuntu
sudo apt update && sudo apt install --reinstall ca-certificates
sudo update-ca-certificates

# AlmaLinux / Rocky / CentOS
sudo dnf reinstall ca-certificates
sudo update-ca-trust extract

Re-test right away:

curl -v https://example.com 2>&1 | grep -i "SSL certificate"

No output means it passed. If you're inside a Docker container, the fix is the same but has to happen inside the image — add this to your Dockerfile rather than the host:

RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates

2. Fix PHP specifically, if it's PHP that's failing

WordPress's HTTP API, Composer, and custom PHP scripts all go through PHP's own cURL/OpenSSL config, which can point at a separate CA file. Check what's set in cPanel's MultiPHP INI Editor or directly in php.ini:

php -i | grep -i cainfo

If curl.cainfo points at a path that doesn't exist, either clear the directive (so PHP falls back to the system store you just fixed) or point it at a current bundle, commonly /etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu) or /etc/pki/tls/certs/ca-bundle.crt (RHEL-based). Restart PHP-FPM after any change:

sudo systemctl restart php-fpm

3. If it's a private/self-signed CA, add it manually

Don't disable verification — add the certificate instead. Drop the CA's public certificate into the trust store and rebuild:

# Debian / Ubuntu
sudo cp your-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

# AlmaLinux / Rocky
sudo cp your-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

4. Check the clock

timedatectl status

If the time is off by more than a few minutes, sync it with chrony or systemd-timesyncd before you chase anything else — a wrong clock can produce the exact same error and waste an hour of debugging.

What NOT to do

It's tempting to reach for curl -k, CURLOPT_SSL_VERIFYPEER => false, or git config http.sslVerify false to make the error go away. Don't leave these in place on anything that touches production — you're disabling the exact check that protects you from a man-in-the-middle attack. Use them only as a one-line diagnostic to confirm the CA store is really the cause, then fix the store and remove the flag.

Prevention

HabitWhy it matters
Keep the OS patched (apt upgrade / dnf upgrade on a schedule)ca-certificates updates ride along with routine OS patching
Install ca-certificates explicitly in any Docker/minimal imageSlim base images don't include it by default
Avoid custom curl.cainfo paths unless you have a reasonThe system-managed bundle stays current on its own; a hardcoded path won't
Sync time with NTP/chronyCertificate validation depends on an accurate clock
Test outbound HTTPS after any migration or new server buildCatches a stale trust store before it breaks a cron job in production

If you're moving to a new VPS or spinning up a fresh one on SkyServer, run a quick curl -v https://google.com and curl -v https://github.com right after setup, before anything depends on outbound HTTPS. Thirty seconds now saves a confusing debugging session later when a cron job or deployment script silently fails at 2 a.m.

Frequently Asked Questions

Why does the browser work but curl doesn't?

Browsers maintain and update their own certificate trust store independently of the operating system. Command-line tools like curl, git, and PHP's cURL extension rely on the OS-level ca-certificates bundle instead, which only updates when you patch the server. That's why the two can disagree about the same certificate.

Is it safe to just use curl -k to bypass the error?

Only as a temporary diagnostic step to confirm the CA store is the cause. Leaving -k (or the equivalent verify-false setting) in a script or cron job disables certificate validation entirely, which exposes that connection to interception. Fix the trust store instead.

I updated ca-certificates and it still fails — now what?

Check whether the specific tool has its own CA setting overriding the system store — PHP's curl.cainfo, Python's REQUESTS_CA_BUNDLE, or an app-specific config are common culprits. Also confirm the server's clock is correct with timedatectl status, since chain validation depends on it.

Does this affect my site's visitors too?

No. Visitors' browsers use their own up-to-date trust stores, so this issue is invisible to them. It only affects processes running on the server itself — cron jobs, deployment scripts, WordPress's outbound HTTP calls, Composer, and similar tools.

Does this happen inside Docker containers on a VPS?

Yes, and it's actually more common there. Minimal base images like Alpine or Debian-slim often don't include ca-certificates at all, so you need to install it explicitly in the Dockerfile rather than relying on the host's trust store, which containers don't share by default.