Sign the `.exe` *after* PyInstaller builds it, with a code-signing certificate plus a timestamp. On Windows use signtool; on Linux or CI use osslsigncode (no Windows machine needed). Signing embeds a verified publisher identity that clears antivirus false positives and starts building SmartScreen reputation. You need a CA-issued OV or EV certificate (or Microsoft Trusted Signing) — a self-signed cert lets you test the mechanism but Windows won't trust it. Always add an RFC3161 timestamp so the signature stays valid after the certificate expires. I tested the full sign-and-verify flow below.
Code signing is the real, permanent fix for a PyInstaller .exe that antivirus flags or SmartScreen warns about — it attaches a verified identity to your build so Windows knows who published it. This guide covers the whole thing: what a signature actually does, which certificate you need (and a change that surprises people), and the exact commands to sign on Windows *and* from a Linux CI runner. I ran the Linux/osslsigncode path end to end so you can see the real sign, timestamp and verify output.
What signing does — and what it doesn't
A code signature binds your executable to a publisher identity verified by a certificate authority, and proves the file hasn't been altered since you signed it. Concretely, that:
- Clears most antivirus false positives — a signed binary from a known publisher is far less likely to trip heuristic/ML detection than an anonymous unsigned one.
- Starts SmartScreen reputation — the download/run warning fades as your signed app accumulates installs under the same identity.
- Shows a real publisher in the UAC prompt and file Properties instead of "Unknown publisher."
Signing does NOT protect or hide your source code — a signed PyInstaller .exe still unpacks to readable bytecode. And it does not instantly bypass SmartScreen: since Microsoft's change, even EV certificates build reputation over download volume rather than granting instant trust. To protect the code itself, obfuscate the source before packaging; signing is about trust, not secrecy.
You need a certificate — the options (and a catch)
You can't self-sign your way to trust: Windows only trusts a certificate chained to a CA it already trusts. Your choices:
- OV (Organization Validation) — the standard, cheapest CA option. The CA verifies your business exists. Good enough to clear AV flags and build reputation.
- EV (Extended Validation) — stricter identity checks, historically instant SmartScreen trust (no longer guaranteed). More expensive.
- Microsoft Trusted Signing (formerly Azure Code Signing) — a low-cost, cloud-based signing service; no physical token to manage, and it integrates cleanly with CI. Often the easiest modern route for individuals and small teams.
The catch that surprises people: since June 2023 the industry baseline requires OV signing keys to live on a hardware token or HSM (EV always did). So a modern CA cert usually ships as a USB token or a cloud-HSM handle — you often can't just download a .pfx file any more. Trusted Signing sidesteps this because the key stays in Microsoft's cloud HSM.
Sign on Windows with signtool
signtool ships with the Windows SDK. With your certificate installed (from its token/store), sign and timestamp in one command:
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a myapp.exe:: verify it afterwardssigntool verify /pa /v myapp.exe
/fd SHA256 sets the file digest, /tr + /td add an RFC3161 timestamp (see below), and /a auto-selects the best signing cert in your store. For a Trusted Signing setup you use the Azure.CodeSigning dlib instead of a local cert; Microsoft's docs and GitHub Action wire it up.
Sign from Linux or CI with osslsigncode (tested)
You don't need Windows to sign a Windows .exe. osslsigncode does Authenticode signing on Linux and macOS, which is what you want in a GitHub Actions or Docker pipeline. Here's the real flow I ran against a PyInstaller build. First, for the demo, a self-signed code-signing cert (in production you'd point at your CA cert or token instead):
openssl req -x509 -newkey rsa:3072 -keyout key.pem -out cert.pem -days 365 -nodes \-subj "/CN=Acme Ltd/O=Acme Ltd/C=US" \-addext "keyUsage=digitalSignature" \-addext "extendedKeyUsage=codeSigning"
The unsigned PyInstaller .exe has no signature at all:
$ osslsigncode verify myapp.exeNo signature foundUnable to extract existing signatureFailed
Now sign it, adding a timestamp from a trusted authority:
osslsigncode sign -certs cert.pem -key key.pem \-n "MyApp CLI tool" -i "https://acme.example" \-h sha256 -ts http://timestamp.digicert.com \-in myapp.exe -out myapp-signed.exe
Connecting to http://timestamp.digicert.comSucceeded
And verifying the signed file shows the embedded Authenticode signature — with the digest of the file matching, which proves the executable content is intact and covered by the signature:
$ osslsigncode verify myapp-signed.exeSignature Index: 0 (Primary Signature)Message digest algorithm : SHA256Current message digest : DE8303C77214A7912650D64B77A96711929A16EED382BA84...Calculated message digest : DE8303C77214A7912650D64B77A96711929A16EED382BA84...Subject: /CN=Acme Ltd/O=Acme Ltd/C=USIssuer : /CN=Acme Ltd/O=Acme Ltd/C=USCountersignatures:Timestamp time: Sep 1 09:59:58 2026 GMTIssuer: /C=US/O=DigiCert, Inc./CN=DigiCert Trusted G4 TimeStamping ...
The signature added about 8 KB to the file (5,998,560 -> 6,006,592 bytes) and left the program itself untouched. With a self-signed cert like this, Subject equals Issuer and Windows won't trust the chain — the mechanism works, but real-world trust needs a CA cert (swap -certs/-key for -pkcs12 yourcert.pfx). The timestamp, though, is real and from DigiCert.
Always add a timestamp
A code-signing certificate expires (typically in 1–3 years), but software you shipped should keep working long after. An RFC3161 timestamp — the -ts / /tr flag above — records *when* you signed, countersigned by a timestamp authority. So even after your cert expires, Windows sees "this was validly signed on a date the cert was active" and keeps trusting it. Sign without a timestamp and every signature silently dies with the certificate.
In my test the countersignature came back from DigiCert's timestamp authority (visible in the verify output). Common free timestamp URLs: http://timestamp.digicert.com and http://timestamp.sectigo.com. Always include one.
Sign in GitHub Actions
The clean way to sign is in CI, right after the build, so the binary is never touched by a developer machine. Store the certificate (base64 of the .pfx) and its password as encrypted repository secrets, then decode and sign in the workflow with osslsigncode:
- name: Sign the executableenv:CERT_B64: ${{ secrets.CODESIGN_PFX_B64 }}CERT_PW: ${{ secrets.CODESIGN_PFX_PASSWORD }}run: |echo "$CERT_B64" | base64 -d > cert.pfxosslsigncode sign -pkcs12 cert.pfx -pass "$CERT_PW" \-h sha256 -ts http://timestamp.digicert.com \-in dist/myapp.exe -out dist/myapp-signed.exerm -f cert.pfx
If you use Microsoft Trusted Signing, there's an official GitHub Action that signs with the cloud key instead of a stored .pfx — no secret to leak. Either way, sign the artifact the build job produced, then attach the *signed* file to your release.
The SmartScreen reality
Set expectations: signing removes the malware-style flag and makes the publisher known, but a brand-new signed app can still show a SmartScreen prompt until it earns reputation through downloads. This is the biggest change from a few years ago — EV certificates used to grant instant SmartScreen trust, and no longer reliably do. Reputation now accrues to your consistent publisher identity over install volume, for both OV and EV. So sign every release with the *same* certificate, ship consistently, and the warnings fade. For the false-positive side of this, see PyInstaller EXE flagged as a virus.
Build, then sign
Signing is the last step of a good release, not a substitute for the earlier ones. Build a clean, metadata-carrying executable first — our free Python-to-EXE builder produces a real PyInstaller .exe with embedded version metadata plus a GitHub Actions workflow you can drop this signing step into. Build it reproducibly, sign it in CI, timestamp it, and ship the signed artifact.
Build the .exe you'll sign
Our free Python-to-EXE builder produces a real PyInstaller executable with version metadata and a GitHub Actions workflow — add the signing step from this guide.
Open the Python-to-EXE builderFree tools mentioned here
Related guides
Frequently asked questions
Can I code-sign a Windows .exe without a Windows machine?
Yes. osslsigncode performs Authenticode signing on Linux and macOS, so you can sign a PyInstaller .exe in a Docker container or a GitHub Actions runner without Windows. In testing it signed and timestamped a real PyInstaller build and the signature verified correctly. The command is osslsigncode sign -pkcs12 cert.pfx -pass <pw> -ts <url> -in app.exe -out signed.exe.
Do I really need to buy a certificate — will a self-signed one work?
A self-signed certificate lets you test the signing process, and the signature embeds and verifies correctly, but Windows won't trust it (the subject equals the issuer, with no CA chain), so it doesn't clear SmartScreen or antivirus for your users. For real-world trust you need a CA-issued OV or EV certificate, or Microsoft Trusted Signing.
Why does my signature need a timestamp?
A code-signing certificate expires in a year or two, but your shipped software should keep working. An RFC3161 timestamp records the date you signed, countersigned by a timestamp authority, so Windows keeps trusting the signature even after the cert expires because it was valid when applied. Without a timestamp, every signature dies with the certificate. Add it with signtool's /tr or osslsigncode's -ts flag.
Will code signing stop Windows Defender flagging my PyInstaller exe?
Signing is the most reliable fix — it attaches a verified publisher identity, which clears most heuristic false positives and starts building SmartScreen reputation. It won't grant instant trust: since Microsoft's change, even EV certificates build reputation over download volume, so a freshly signed app may still prompt until it's established. Sign every release with the same cert so reputation accrues.
Does signing a PyInstaller exe change or protect the code inside?
No. Signing appends a signature to the file's certificate table without altering the program (the verify output confirms the file digest is unchanged), and it does not hide or encrypt your code — a signed PyInstaller .exe still unpacks to readable bytecode. Signing is about trust and integrity, not secrecy. To protect the source, obfuscate it before packaging.