-
Notifications
You must be signed in to change notification settings - Fork 15
HTTPS Certificates and CA Bundles
If a webhook just failed with
unable to get local issuer certificate, start here. Your webhook is probably fine. The server it runs on hasn't been told which certificate authorities to trust, so it can't confirm it's really talking to Slack or Discord. It's a one-time server fix, and it takes about five minutes.
This is the operations-side companion to the in-app guide at Workflows β Help β HTTPS certificates (workflow/help-ssl.php).
β οΈ Wrong page if a client can't trust FreeITSM. This page is about outbound calls β FreeITSM reaching Slack, Discord or a webhook endpoint and not being able to verify them. If your symptom is the inventory agent refusing to post with "Could not establish trust relationship for the SSL/TLS secure channel", that's the inbound direction and nothing here will fix it. See The inventory agent instead.
Since #919, FreeITSM ships its own CA bundle (
includes/cacert.pem) and points cURL at it automatically, so a fresh install now verifies out of the box β the manualphp.inisteps below are no longer required on a stock setup. They remain the reference for configuring a system-wide bundle, for the CLI worker, and for the edge cases in Troubleshooting. Verification is now governed by a single global switch; see SSL Verification β the Global Setting.
Transport error: SSL certificate problem: unable to get local issuer certificate
Variants that mean the same thing:
certificate verify failed-
server certificate verification failed. CAfile: none(Linux/GnuTLS wording) self-signed certificate in certificate chainunable to get issuer certificate
Read the rest of the panel before changing anything. If FreeITSM showed you the JSON it built under Sent (sample data), your workflow, your variables and your payload format all worked. Only the final network hop failed.
When FreeITSM posts to https://discord.com/β¦, two separate things must happen:
- Encryption β scramble the traffic so nobody in between can read it.
- Verification β confirm the server on the other end really is Discord, and not someone impersonating it.
Encryption without verification is close to worthless: you'd have a beautifully encrypted conversation with an impostor.
Verification works like checking a passport. Discord presents a certificate saying "I am discord.com", signed by a certificate authority (CA) β an organisation whose job is vouching for identities. Your server checks that signature against its own list of trusted authorities.
The error above is not "this certificate is fake". It's something more basic: "I have no list of authorities, so I can't check anybody's passport."
A CA bundle is that list. It's a plain text file β conventionally cacert.pem β containing the public certificates of the ~120β150 organisations the world has agreed to trust. Open it in a text editor and you'll see block after block of -----BEGIN CERTIFICATE-----.
The one almost everyone uses is the Mozilla CA bundle: the trust list that ships inside Firefox, extracted and republished by the curl project. Using it means your server trusts exactly who a mainstream browser trusts.
What it is not:
- It contains no secrets. These are public certificates. Safe to read, copy, back up, commit to a private repo.
- It is not FreeITSM-specific, or webhook-specific, or Discord-specific. It's the same list every other program uses.
- It is not a licence or an account. It's free; you download it.
It goes stale slowly as authorities are added or withdrawn, so re-downloading once a year is good hygiene. Nothing breaks immediately if you don't.
On Linux, the OS maintains a CA bundle and PHP finds it automatically. Most people never learn any of this exists.
On Windows, PHP itself ships with no CA bundle and no pointer to one. The two settings that would tell it where to look β curl.cainfo and openssl.cafile β are commented out in the default php.ini, and WAMP supplies no cacert.pem. So raw PHP on a stock Windows/WAMP box cannot make a verified HTTPS request to anything, out of the box.
FreeITSM works around this for you. Since #919 it ships its own
includes/cacert.pemand points cURL at it automatically, so FreeITSM specifically verifies out of the box even when php.ini is empty. This section explains the underlying PHP situation β useful background, and it matters if you ever see this error, but you usually won't have to touch php.ini. See Fixing it for what to actually do.
Since FreeITSM ships its own bundle, you should only be here if the /setup check went red ("on, but cannot verify") β meaning the shipped includes/cacert.pem is missing or unreadable β or you'd rather manage the trust list yourself. Two ways, easiest first.
FreeITSM automatically uses includes/cacert.pem whenever php.ini has no bundle configured. So if the shipped one is missing, or you want to refresh a stale one:
- Download the Mozilla bundle from its canonical home β
https://curl.se/ca/cacert.pem(~200 KB, full of-----BEGIN CERTIFICATE-----blocks; if it's a few hundred bytes of HTML, your browser saved an error page). - Save it as
includes/cacert.pemin the application folder. - Reload the page. No php.ini editing, no Apache restart.
This is the fix the /setup page points you to, and it covers everything in one step β the browser app and the background worker β because FreeITSM attaches the bundle in code (sslApplyCurl), not through php.ini. So the "two php.ini files" trap below can't bite you.
Do this only if you'd rather every PHP application on the server share one bundle, or you're on a Linux image with no system certificates. It's the traditional route and still works; it just isn't required for FreeITSM on its own.
From https://curl.se/ca/cacert.pem. Save it outside your versioned PHP folder, so upgrading PHP doesn't delete it β e.g. C:\wamp64\cacert.pem.
WAMP keeps two, and using only one is the classic way to half-fix this:
| Which | Typical path | Why it matters |
|---|---|---|
| Apache's | C:\wamp64\bin\apache\apache<version>\bin\php.ini |
Everything you do in the browser β including the Send test button. |
| The CLI's | C:\wamp64\bin\php\php<version>\php.ini |
Scheduled tasks β including the background webhook delivery worker. Miss this and Send test passes while real deliveries keep failing. |
In each, find these two lines (commented out with a leading ;), remove the semicolon, set the path:
curl.cainfo = "C:/wamp64/cacert.pem"
openssl.cafile = "C:/wamp64/cacert.pem"Forward slashes; keep the quotes. Both are needed β curl.cainfo covers cURL, openssl.cafile covers PHP's other stream-based HTTPS calls.
php.ini is read once at startup. WAMP tray icon β Restart All Services. (The CLI re-reads its ini on every run, so it needs no restart.)
Easiest: press Send test on the webhook action again. You want Delivered, an HTTP 204 (Discord) or 200, and the message actually appearing in your channel.
To check the setting rather than the symptom, drop this in the web root as catest.php, load it, then delete it:
<?php
header('Content-Type: text/plain');
echo "curl.cainfo = " . (ini_get('curl.cainfo') ?: '(EMPTY - not set)') . "\n";
echo "openssl.cafile = " . (ini_get('openssl.cafile') ?: '(EMPTY - not set)') . "\n";
$ch = curl_init("https://curl.se/");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
echo curl_exec($ch) === false
? "TLS: FAILED - " . curl_error($ch) . "\n"
: "TLS: OK - certificate verified\n";Both paths printed, TLS OK. If the paths still read EMPTY, Apache is on the old config β wrong php.ini, or not restarted.
Don't forget the worker (Option B only). A passing Send test only proves Apache's PHP is fixed. Real webhooks are delivered by the background worker under the CLI PHP. Tests passing while live deliveries fail = you edited only one of the two php.ini files. Option A sidesteps this entirely β the shipped
includes/cacert.pemis attached in code, so it covers both the browser app and the worker at once.
Searching this error online will suggest "just disable SSL verification". It makes the error go away. Here's the cost.
β οΈ Disabling verification means FreeITSM stops checking who it is talking to. It still encrypts β but it will hand your ticket data, your customers' email addresses and your webhook signing secret to anyone who intercepts the connection and claims to be Discord. That is precisely the man-in-the-middle attack verification exists to prevent. You'd be turning off the lock because you couldn't find the key.
The webhook transport always verifies (CURLOPT_SSL_VERIFYPEER => true, includes/webhook_delivery.php) and offers no setting to change that. Webhooks carry record data to third parties over the public internet β the worst possible place to stop checking identities. Install the CA bundle instead.
Since #919 there are no per-module "Verify SSL" toggles. Certificate verification for every outbound call β AI providers, mailboxes, SSO, Intune, vCenter, share and reset email β is governed by a single setting, SSL_VERIFY_PEER in config.php, applied through the shared sslApplyCurl() helper. It ships on. Turning it off disables verification everywhere at once, so it is not a casual switch. See SSL Verification β the Global Setting for the full design.
The only legitimate case for touching it is a corporate network that intercepts outbound TLS with an inspection proxy presenting a certificate signed by an internal authority your server has never heard of. Even then, the correct fix is to append your company's internal root certificate to cacert.pem, so your server trusts the proxy legitimately and keeps verifying everything else β never to disable verification. Whoever runs your network will have that root certificate ready, because every other application on the network needs it too.
β οΈ vCenter/ESXi with a self-signed certificate now verifies where it did not before (#919). Add that host's certificate tocacert.pem, or the asset sync will fail.
Rule of thumb. Turning verification off is only ever defensible for a service inside your own network that you fully control, and even then it's a stopgap. For anything on the public internet β Slack, Discord, Teams, OpenAI, Anthropic β it is never the right answer. If it "fixed" your problem, what it actually did was hide it.
| What you see | What it usually means |
|---|---|
Check page still prints (EMPTY - not set)
|
Apache is on the old config. Restart it β and make sure you edited Apache's php.ini, not just the CLI one. WAMP tray β PHP β php.ini opens the right file. |
| Send test passes, live deliveries fail | The classic half-fix: Apache's ini done, CLI's not. The worker runs under the CLI PHP. Do both. |
self-signed certificate in certificate chain |
Something is intercepting your TLS β a corporate inspection proxy, or antivirus that scans HTTPS. Append that product's root certificate to cacert.pem. Don't disable verification. |
Still unable to get local issuer certificate
|
Check the php.ini path actually points at the file (typos and backslashes bite), and that cacert.pem is a real bundle rather than a saved HTML error page. |
Could not resolve host |
Not a certificate problem β DNS. Check the URL for a typo and that the server can reach the internet. |
webhookDiagnoseError() (includes/webhook_delivery.php) recognises the TLS-trust family β plus DNS, connection-refused and timeout β and returns a plain-English explanation with a link to the in-app version of this page. It renders in both places a webhook failure appears:
- the workflow editor's Send test panel, and
- the System β Webhooks delivery log.
Diagnosis happens at render time from the stored error, so historic failed deliveries get the explanation retroactively β no schema change, nothing to backfill.
See also: Workflow & Webhook Pitfalls for why this error was worth an entire page.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)