SSL errors when calling the Paystack API mean your server does not trust the certificate presented by api.paystack.co. The most common causes are an expired CA certificate bundle on your server, a missing root CA certificate, a corporate proxy intercepting TLS traffic, or a misconfigured Node.js environment. The fix is to update your CA bundle (update-ca-certificates on Linux or upgrade Node.js), not to set NODE_TLS_REJECT_UNAUTHORIZED=0. Disabling certificate verification on a payment API connection exposes you to man-in-the-middle attacks where an attacker can steal your API keys and customer data.
SSL/TLS errors manifest differently depending on your language and HTTP library:
Node.js (native fetch or node-fetch):
Error: unable to verify the first certificate
Error: self signed certificate in certificate chain
Error: UNABLE_TO_VERIFY_LEAF_SIGNATURE
Error: certificate has expired
Node.js (axios):
Error: unable to verify the first certificate
at TLSSocket.onConnectSecure (node:tls:...)
code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
Python (requests):
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.paystack.co', port=443):
Max retries exceeded with url: /transaction/initialize
(Caused by SSLError(SSLCertVerificationError(1,
'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate')))
PHP (cURL):
cURL error 60: SSL certificate problem: unable to get local issuer certificate
All of these mean the same thing: your server tried to establish a TLS connection to api.paystack.co and could not verify the certificate chain. The connection was refused by your own TLS library to protect you from a potential man-in-the-middle attack.
Your operating system and runtime (Node.js, Python, PHP) ship with a bundle of trusted root CA certificates. When a CA issues new certificates or rotates their root certificate, your bundle needs to be updated. If it is not, connections to sites using the new certificates will fail.
This is common on:
Fix for Ubuntu/Debian:
# Update the CA certificate bundle
sudo apt-get update && sudo apt-get install -y ca-certificates
sudo update-ca-certificates
Fix for CentOS/RHEL:
sudo yum update ca-certificates
Fix for Alpine (Docker):
# In your Dockerfile
RUN apk add --no-cache ca-certificates
Fix for Node.js:
# Upgrade to the latest LTS version
nvm install --lts
nvm use --lts
# Node.js 18+ uses the operating system's CA store by default
# Older versions bundle their own, which may be outdated
After updating, restart your application and try the API call again.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
TLS certificates form a chain: your server trusts a root CA, the root CA signs an intermediate CA, and the intermediate CA signs the server certificate. If your CA bundle has the root but not the intermediate, the chain is broken.
This manifests as "unable to verify the first certificate" in Node.js. The "first certificate" is the server certificate (api.paystack.co), and it cannot be verified because the intermediate certificate that signed it is missing from your trust store.
Diagnosis:
# Check the certificate chain from your server
openssl s_client -connect api.paystack.co:443 -showcerts
This command shows the full certificate chain. If you see "verify return code: 0 (ok)", the chain is valid from the internet. The problem is on your server.
# Check what your server trusts
openssl s_client -connect api.paystack.co:443 -CApath /etc/ssl/certs/
If this fails but the previous command (without -CApath) succeeds, your /etc/ssl/certs/ directory is missing the required certificates.
Fix: Update your CA certificates as shown in Cause 1. If that does not work, you may need to manually install the intermediate certificate:
# Download the intermediate certificate and add it to your trust store
# The specific intermediate depends on Paystack's certificate issuer
# Check the openssl output above for the issuer details
sudo cp intermediate-cert.pem /usr/local/share/ca-certificates/
sudo update-ca-certificates
Many corporate networks run a TLS inspection proxy. When your application connects to api.paystack.co, the proxy intercepts the connection, decrypts it, inspects the traffic, and re-encrypts it using its own certificate. Your application sees the proxy's certificate instead of Paystack's certificate and rejects it.
Signs that a proxy is the problem:
Fix: Add your corporate proxy's CA certificate to your application's trust store.
// Node.js: Add a custom CA certificate
// Get the proxy CA cert from your IT department (usually a .pem or .crt file)
// Option 1: Environment variable (recommended)
// Set this before starting your Node.js app:
// NODE_EXTRA_CA_CERTS=/path/to/corporate-proxy-ca.pem node app.js
// Option 2: In code (less preferred but works)
var https = require('https');
var fs = require('fs');
var proxyCACert = fs.readFileSync('/path/to/corporate-proxy-ca.pem');
var agent = new https.Agent({
ca: proxyCACert,
});
The NODE_EXTRA_CA_CERTS environment variable is the cleanest approach. It adds the certificate to Node.js's trust store without modifying your application code.
Minimal Docker base images (like node:alpine or python:slim) may not include a complete CA certificate bundle. Your application works fine on your development machine but fails with SSL errors in the container.
Fix for Node.js Alpine:
# Dockerfile
FROM node:20-alpine
# Add CA certificates
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["node", "server.js"]
Fix for Python slim:
# Dockerfile
FROM python:3.12-slim
# Add CA certificates
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Fix for Ubuntu-based images:
# Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
# ... rest of your Dockerfile
Always include ca-certificates in your Dockerfile if your application makes HTTPS calls. This is not Paystack-specific. Any HTTPS connection will fail without it.
You will find Stack Overflow answers and blog posts suggesting these "fixes":
// NEVER DO THIS for a payment integration
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// NEVER DO THIS
var agent = new https.Agent({ rejectUnauthorized: false });
# NEVER DO THIS (Python)
requests.post(url, verify=False)
// NEVER DO THIS (PHP)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
These "solutions" disable certificate verification entirely. Here is what that means for a payment integration:
The "it works in development" argument is irrelevant. If this code ships to production, every API call your server makes is vulnerable. Fix the root cause (update CA bundle, add proxy cert) instead of disabling the security that protects your customers' money.
SSL certificates have validity periods. If your server's clock is wrong (set to a date in the past or far future), a valid certificate may appear expired or not-yet-valid.
This is rare on cloud servers (AWS, GCP, Azure sync time automatically) but common on:
Diagnosis:
# Check your system time
date
# Should match the current time within a few seconds
# Check against an NTP server
ntpdate -q pool.ntp.org
Fix:
# Sync time with NTP
sudo ntpdate pool.ntp.org
# Or enable automatic time sync
sudo timedatectl set-ntp true
If your server clock was significantly off, restart your application after fixing the time. Some TLS libraries cache certificate validation results.
Run this script on your server to identify the exact SSL problem:
// diagnose-ssl.js
// Run: node diagnose-ssl.js
var https = require('https');
var tls = require('tls');
function diagnoseSsl() {
console.log('=== Paystack SSL Diagnosis ===\n');
// Check Node.js version
console.log('Node.js version: ' + process.version);
console.log('OpenSSL version: ' + process.versions.openssl);
console.log('TLS default min version: ' + tls.DEFAULT_MIN_VERSION);
console.log('');
// Check if NODE_TLS_REJECT_UNAUTHORIZED is set (bad!)
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
console.error(
'WARNING: NODE_TLS_REJECT_UNAUTHORIZED is set to 0. ' +
'Certificate verification is DISABLED. This is a security risk.'
);
}
// Check NODE_EXTRA_CA_CERTS
if (process.env.NODE_EXTRA_CA_CERTS) {
console.log('NODE_EXTRA_CA_CERTS: ' + process.env.NODE_EXTRA_CA_CERTS);
} else {
console.log('NODE_EXTRA_CA_CERTS: not set');
}
console.log('');
// Try connecting to api.paystack.co
console.log('Testing connection to api.paystack.co:443...');
var options = {
host: 'api.paystack.co',
port: 443,
method: 'GET',
path: '/bank?perPage=1',
headers: {
Authorization: 'Bearer sk_test_placeholder',
},
};
var req = https.request(options, function(res) {
console.log('HTTP Status: ' + res.statusCode);
console.log('TLS connection successful.');
// Get certificate details
var socket = res.socket;
var cert = socket.getPeerCertificate();
console.log('\nCertificate details:');
console.log(' Subject: ' + (cert.subject ? cert.subject.CN : 'unknown'));
console.log(' Issuer: ' + (cert.issuer ? cert.issuer.O : 'unknown'));
console.log(' Valid from: ' + cert.valid_from);
console.log(' Valid to: ' + cert.valid_to);
console.log(' Protocol: ' + socket.getProtocol());
res.on('data', function() {});
res.on('end', function() {
console.log('\nResult: SSL is working correctly. No issues detected.');
});
});
req.on('error', function(err) {
console.error('\nConnection FAILED: ' + err.message);
console.error('Error code: ' + err.code);
if (err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
console.error('\nDiagnosis: Missing intermediate CA certificate.');
console.error('Fix: Update your CA bundle with "sudo update-ca-certificates" (Debian/Ubuntu)');
console.error(' or "sudo yum update ca-certificates" (CentOS/RHEL)');
} else if (err.code === 'CERT_HAS_EXPIRED') {
console.error('\nDiagnosis: A certificate in the chain has expired.');
console.error('Fix: Update your CA bundle and check your system clock.');
} else if (err.code === 'SELF_SIGNED_CERT_IN_CHAIN') {
console.error('\nDiagnosis: A proxy or firewall is intercepting the connection.');
console.error('Fix: Add the proxy CA certificate to NODE_EXTRA_CA_CERTS.');
} else if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
console.error('\nDiagnosis: Certificate hostname mismatch. A proxy may be intercepting.');
console.error('Fix: Check your DNS and proxy configuration.');
}
});
req.end();
}
diagnoseSsl();
Run this script on any server experiencing SSL errors with Paystack. It will tell you the exact problem and the fix.
After applying the fix, verify it works:
Step 1: Run the diagnosis script.
Execute the diagnosis script from the previous section. Confirm it shows "SSL is working correctly" with a valid certificate.
Step 2: Make a test API call.
Call a lightweight Paystack endpoint and confirm it succeeds.
// Quick verification: call the bank list endpoint
function verifySsl() {
fetch('https://api.paystack.co/bank?perPage=1', {
headers: {
'Authorization': 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.status) {
console.log('SSL is working. API responded successfully.');
console.log('Bank count: ' + data.data.length);
} else {
console.error('API responded but with an error: ' + data.message);
}
})
.catch(function(err) {
if (err.message.includes('certificate') ||
err.message.includes('SSL') ||
err.message.includes('TLS')) {
console.error('SSL error still present: ' + err.message);
} else {
console.error('Non-SSL error: ' + err.message);
}
});
}
verifySsl();
Step 3: Check the certificate chain.
Run openssl s_client -connect api.paystack.co:443 from your server and confirm the verify return code is 0.
Step 4: Confirm NODE_TLS_REJECT_UNAUTHORIZED is not set.
Check your environment variables, .env file, Dockerfile, and deployment configuration. If it is set to 0 anywhere, remove it.
Step 5: Test from your Docker container.
If you deploy with Docker, build the updated image and run the verification script inside the container. Do not test only on your host machine.
For webhook-related SSL issues (Paystack cannot reach your webhook URL because of your server's SSL certificate), see Paystack Webhook Not Firing: Diagnostic Checklist. For mixed content errors on the frontend, see Paystack Mixed Content and HTTPS Errors.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course