The short answer
An Electron app needs two different certificates, one per platform. On Windows you sign with an OV or EV code signing certificate from a publicly trusted CA, and since 1 June 2023 its private key has to live on certified hardware — a token, an HSM, or a cloud signing service — so the .pfx file that most electron-builder tutorials still show can no longer be issued. On macOS that certificate is useless: Apple accepts only a Developer ID Application certificate, followed by notarization. electron-builder drives both, but the Windows half is configured through win.signtoolOptions or win.azureSignOptions, never through certificateFile.
On this page
Electron signing has a documentation problem. The framework has been around long enough that the top results still describe a workflow that stopped being possible in 2023: download a PFX, drop the path into certificateFile, set a password, ship. Every part of that is now wrong for a publicly trusted certificate, and following it costs a day before the error message even hints why. This guide covers what actually works in electron-builder 26 — which certificate each platform needs, the three ways to reach a hardware-held key, the CI constraint that catches teams after they have already bought, and the extra macOS steps that have nothing to do with the certificate at all.
Which certificate does an Electron app need?
One per platform you ship, and they come from different places. Windows needs an OV or EV code signing certificate from a CA in the Microsoft Trusted Root Program. macOS needs a Developer ID Application certificate, which only Apple issues and only to Apple Developer Program members. Linux builds need neither — AppImage and .deb packages are trusted through repositories and checksums instead.
The Windows certificate
OV and EV both produce a valid Authenticode signature and both replace the "Unknown Publisher" line in the UAC prompt with your organization name. The difference shows up in SmartScreen: a fresh OV signature starts with no reputation and earns it as copies are downloaded and run without incident, while EV validation carries weight from the first release. If your installer reaches a handful of internal users, OV is fine. If a red SmartScreen block during launch week would cost you real signups, EV shortens that period. My-SSL sells both as Certum code signing certificates, and the EV versus OV comparison goes through the validation difference in detail.
What is no longer negotiable is where the key lives. Since 1 June 2023 the CA/Browser Forum has required every publicly trusted code signing key — OV as well as EV — to be generated inside hardware meeting FIPS 140-2 Level 2 or Common Criteria EAL4+, with export disabled. In practice a CA hands you a USB token, a cloud signing subscription, or an HSM integration, and there is simply no file to download. Certificates issued or renewed on or after 1 March 2026 also carry a 460-day maximum validity, so plan the renewal into your release calendar.
The macOS certificate
Apple runs its own CA for this and does not recognise anyone else's. A Developer ID Application certificate requires an active Apple Developer Program membership, and it is the only certificate Gatekeeper will accept for an app distributed outside the Mac App Store. Signing is also not sufficient on its own — the app has to be notarized by Apple afterwards. No public CA, including the one that issues your Windows certificate, can substitute for any part of that chain.
What electron-builder actually signs
On Windows it signs the packaged executable, any native binaries bundled beside it, and then the installer that wraps them — the installer last, because it embeds the files it has just signed. It does not sign your JavaScript. That lives in an asar archive, which Authenticode has no way to attach a signature to, and it does not sign the auto-update metadata either.
Two consequences are worth internalising before you ship. First, the artefact that matters commercially is the installer: it is what users download, what SmartScreen forms an opinion about, and what accumulates reputation. A build that signs the inner executable but not the setup file shows the same warning as an unsigned build. Second, an Authenticode signature is a statement about origin, not about behaviour. It proves the installer left your build machine unaltered; it does not stop anyone with write access to the installed folder from replacing app.asar afterwards.
The update path deserves its own note. electron-updater checks the publisher name on a downloaded installer against the one recorded at install time before it runs anything, which is what stops a hijacked update feed from pushing an arbitrary binary. Keep publisherName in your Windows configuration matching the CN on your certificate, and remember that switching certificates changes that value — a mismatch stops updates silently on machines running the older release.
Configure Windows signing in electron-builder
In electron-builder 26 the Windows signing settings sit under win.signtoolOptions for certificate-based signing and win.azureSignOptions for Microsoft Trusted Signing. The two are mutually exclusive; set both and Trusted Signing takes over. Because your key is on hardware, you identify the certificate rather than supply it.
Signing against a certificate in the Windows store
A USB token's middleware, or a cloud signing client such as Certum's SimplySign Desktop, presents the certificate to Windows as if it came from a smart card, so it appears in the Current User personal store. SignTool can then find it by subject name or by thumbprint, and the private key operation happens inside the token or the remote HSM. That makes the configuration short:
win:
publisherName: "Example Software Sp. z o.o." # must match the CN on the cert
signtoolOptions:
certificateSubjectName: "Example Software" # substring of the subject
signingHashAlgorithms: ["sha256"]
rfc3161TimeStampServer: "http://time.certum.pl"Use certificateSha1 with the thumbprint instead of certificateSubjectName if more than one certificate in the store could match — during a renewal overlap, for instance, when the old and new certificates share a subject and whichever SignTool picks first is a coin toss. Point rfc3161TimeStampServer at your own CA's endpoint rather than a shared public one; busy pipelines hit rate limits on the popular endpoints and the failure surfaces as an intermittent, confusing build error.
Signing with Microsoft Trusted Signing
Trusted Signing is Microsoft's own service rather than a certificate you hold, and electron-builder talks to it directly. No key material touches the build machine, and it authenticates with Azure credentials, which is what makes it usable on a hosted runner. The trade-off is that it ties your release process to an Azure subscription and to Microsoft's eligibility rules; the comparison with a conventional certificate covers where each one fits.
win:
azureSignOptions:
publisherName: "Example Software Sp. z o.o."
endpoint: "https://eus.codesigning.azure.net"
codeSigningAccountName: "example-signing"
certificateProfileName: "example-public-trust"One version note before you copy any of this into a long-lived project. electron-builder 27, in alpha as of August 2026, replaces both keys with a single win.sign object carrying a type field that selects the backend. If you are on 26 today, write it the way above and run the project's schema migration when you upgrade — the shape changes, the concepts do not.
When you need a custom sign hook
Some signing setups cannot be expressed as SignTool arguments at all: a PKCS#11 module reached through jsign, a CA-supplied signing binary that replaces SignTool, or an HSM CLI that wants its own flags. For those, win.signtoolOptions.sign takes the path of a JavaScript file that electron-builder calls once per file it wants signed, handing you the path and the resolved certificate details.
const { execFileSync } = require("node:child_process");
// electron-builder calls this once for every file it wants signed.
exports.default = async function sign(configuration) {
execFileSync(
"jsign",
[
"--storetype", "PKCS11",
"--keystore", process.env.PKCS11_CONFIG,
"--storepass", process.env.TOKEN_PASSWORD,
"--alias", process.env.CERT_ALIAS,
"--tsaurl", "http://time.certum.pl",
"--tsmode", "RFC3161",
configuration.path,
],
{ stdio: "inherit" },
);
};Three things go wrong here often enough to be worth stating. The hook is invoked for nested files as well as the installer, so it has to be idempotent and quick — a tool that prompts, or that takes ten seconds of handshaking per call, turns a build into a coffee break. It has to throw on failure: returning normally after a failed signing command produces a green build with an unsigned installer, which is the worst of both outcomes. And the secrets it reads should come from the environment, never from the file itself, because this file lives in your repository.
Signing Electron builds in CI/CD
Whether a hosted runner can sign at all depends on how your certificate authenticates, not on which CI system you use. Anything that needs a person — a USB token in a physical port, or a cloud service that opens a signing window after a code from a phone app — cannot run unattended. Only backends that authenticate with a stored secret work on GitHub-hosted or GitLab-shared runners.
Certum's cloud signing is a good illustration because the mechanics are typical. SimplySign Desktop connects using a one-time code generated by the SimplySign mobile app and then keeps the certificate available for a couple of hours. Inside that window, SignTool and therefore electron-builder work exactly as they would with a local token. Outside it, the certificate is simply not in the store and the build fails with a "no certificates were found" error that says nothing about sessions. It is an excellent developer-workstation setup and a poor fit for a nightly pipeline.
That leaves three workable pipeline shapes, and it is worth picking one before you buy rather than after. A self-hosted Windows runner with the token attached and the middleware session kept open is the cheapest path if you already have a machine to spare. A cloud HSM service with API-key authentication — Microsoft Trusted Signing, or an HSM-backed KSP from your CA — is the only option that runs on a hosted runner without a caretaker. Or you split the pipeline: build and test in CI, then sign and publish from a supervised release step, which keeps the release deliberate at the cost of a manual gate. Cloud signing versus a USB token compares the operational cost of each, and code signing in CI/CD covers the pipeline design rules in general.
Fail the build when signing fails. electron-builder will happily produce an unsigned installer if the certificate cannot be found, and a release job that only checks the exit code of the packaging step can publish it. Add an explicit verification step after packaging — the next section has the command — and let that decide whether the artefact is uploaded.
Sign and notarize the macOS build
macOS takes four steps, and signing is only the first. The app is signed with a Developer ID Application certificate under the hardened runtime, uploaded to Apple for notarization, then the returned ticket is stapled to the disk image so Gatekeeper can verify it offline. electron-builder performs all three when it is configured to, and it needs a Mac to do it.
Hardened runtime is a precondition, not an option: Apple refuses to notarize an app without it. Electron apps usually need a couple of entitlements alongside it, because the hardened runtime blocks the JIT and unsigned executable memory that Chromium relies on. Both go in a plist referenced from the mac configuration.
mac:
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
notarize:
teamId: "ABCDE12345"Credentials stay out of the file. The app-specific password route wants an Apple ID, an app-specific password generated at appleid.apple.com, and the team ID, supplied through the environment variables electron-builder's notarization documentation lists. For CI, an App Store Connect API key — passed as appleApiKey and appleApiIssuer — is the better choice, because it does not expire on a password rotation and never triggers a two-factor prompt. Notarization itself is a scan on Apple's side that usually returns within minutes but has no guaranteed turnaround, so treat it as a step that can add time to a release rather than one you can schedule tightly around.
Verify the signature before you ship
Check the installer, not the executable inside it, and check it on a machine that has never seen your certificate. A build machine trusts things a user's laptop does not, so a signature that verifies locally can still fail in the wild — most often because an intermediate certificate was not embedded in the signature.
# Windows — /pa uses the Authenticode policy a user's machine applies
signtool verify /pa /v "dist\MyApp-Setup-1.4.0.exe"
# macOS — the signature, then the notarization ticket
codesign --verify --deep --strict --verbose=2 "dist/mac/MyApp.app"
spctl --assess --type execute --verbose "dist/mac/MyApp.app"
xcrun stapler validate "dist/MyApp-1.4.0.dmg"Gate the pipeline on the exit code rather than on the log text. Both tools return non-zero on failure, and both print output that is easy to misread — spctl in particular says accepted for an app that is signed but not stapled, which is exactly the case you are trying to catch. The signature verification guide goes through what each status value actually means.
Common Electron signing errors
Most Electron signing failures come from four causes: the certificate is not reachable, the configuration points at a key shape that no longer exists, the signature was applied but not to the file users download, or macOS was treated like a Windows problem. Match the symptom below.
"No certificates were found that met all the given criteria"
SignTool looked in the Windows store and found nothing matching. With a token, the device is unplugged or its middleware is not installed for the account running the build. With cloud signing, the session has expired — reconnect the desktop client and try again. It also appears when the build runs elevated or as a service account, because that switches to a different certificate store. Add /debug to a manual signtool sign to see what it considered.
The build succeeds but the installer is unsigned
Signing was skipped rather than failed. The usual reason is that no signing configuration resolved at all — an empty signtoolOptions, or a custom hook that swallowed its own error. electron-builder does not treat a missing certificate as fatal, so add the verification step from the previous section and let it fail the job.
Users on the old release stopped receiving updates
Almost always a publisher name change. electron-updater compares the publisher on the downloaded installer with the one recorded at install time, so a renewed certificate with a slightly different subject — a legal-form change, a new address line — breaks the match. Keep publisherName aligned with the CN, and when a subject really has to change, ship one release signed under the old name that carries users across.
Notarization fails with "The signature does not include a secure timestamp"
The macOS signing step ran without network access to Apple's timestamp service, usually behind a corporate proxy or on a locked-down runner. Signatures made offline are rejected at notarization. Allow outbound access to Apple's timestamp endpoint and re-sign; there is no configuration flag that makes it optional.
"App is damaged and can't be opened" on another Mac
Gatekeeper could not verify the app — commonly because the ticket was never stapled, so verification needs a network call the user's machine could not make, or because the disk image was rebuilt after notarization. Run xcrun stapler validate on the exact file you plan to publish, not on an earlier copy.
SmartScreen still warns even though the installer is signed
Expected for a new OV certificate. The signature is valid — the UAC prompt will show your organization name — but SmartScreen reputation builds with download volume and time. Re-signing does not accelerate it, and changing certificates resets it. The SmartScreen reputation guide explains what does and does not move it.
The bottom line
Budget for two certificates if you ship two platforms, decide how the Windows one will authenticate before you buy it, and verify the installer rather than the executable. If the missing piece is the Windows certificate, My-SSL issues Certum OV and EV code signing certificates with the key held on a token or in Certum's cloud, and the document checklist shows what validation will ask your company for before issuance.