The short answer
PyInstaller does not sign anything for you on Windows. You build the executable, then sign the finished file with SignTool using a certificate whose private key sits on a hardware token or in a CA’s cloud HSM. Since 1 June 2023 there is no downloadable PFX to sign with, which is why almost every Python signing tutorial still online gives a command that cannot work. One thing to settle before you choose a build mode: a signature on a --onefile executable covers the stub the user downloads, not the Python runtime and extension modules that stub unpacks into %TEMP%\_MEIxxxxxx the moment the app starts.
On this page
- What does signing a PyInstaller executable cover?
- Should you build onefile or onedir if you plan to sign?
- Which certificate can you actually buy in 2026?
- Where does signing fit into the build?
- Do you have to sign the installer too?
- Will signing stop SmartScreen, Defender and Smart App Control?
- How do you sign in CI when the key is on a token?
- The 460-day clock and the timestamp that outlives it
- FAQ
What does signing a PyInstaller executable cover?
An Authenticode signature covers one thing: the PE file it is attached to. For a --onedir build that means the launcher, unless you also pass the collected DLLs to SignTool. For a --onefile build it means the single stub the user downloads. Nothing the bootloader writes to disk while the program is starting sits inside that signature.
It helps to know what PyInstaller actually produced. A onefile executable is a small C bootloader with a compressed archive appended after the normal PE content. When the program runs, the bootloader unpacks that archive into a temporary directory named _MEIxxxxxx and starts a second process that imports your code from there. The Python interpreter DLL, every .pyd extension module, the standard library archive and your bundled data files all land on disk at that point, and they land unsigned.
That layout raises an obvious worry, which turns out not to be a problem. If the archive is appended to the end of the file, and Authenticode appends the signature to the end of the file too, does signing not overwrite the thing the bootloader is looking for? PyInstaller searches backwards through the file for its magic cookie instead of assuming a fixed offset from the end, and the project’s own Windows signing recipe documents bolting a SignTool call onto the .spec file. Signing a onefile build is a supported thing to do.
What you cannot do is reach inside the archive afterwards. The same constraint shows up in PyInstaller’s macOS support, where the documentation states plainly that for onefile builds “signing of embedded binaries cannot be performed in a post-processing step” — which is why the macOS path takes a signing identity at build time. On Windows there is no equivalent build-time hook, so the runtime files stay unsigned.
For an ordinary consumer download this is fine. Windows checks the file the user double-clicked, and that file is signed. It stops being fine the moment an enterprise customer’s application control policy walks your install tree, or a security questionnaire asks whether every binary you ship is signed.
Should you build onefile or onedir if you plan to sign?
--onefile gives you one artefact to sign and one artefact to ship, which is why most publishers reach for it. --onedir gives you a launcher plus a directory of libraries, each of which can carry your signature, and it starts faster because nothing has to be unpacked first. Signing is a genuine input to that decision rather than a step you bolt on afterwards.
There is a second reason the choice matters, and it has nothing to do with signatures. A onefile executable extracts dozens of DLLs into a temporary directory on every launch. That is unremarkable behaviour for a packed Python app and completely ordinary behaviour for a dropper, and antivirus heuristics have never been good at telling the two apart. Teams that switch to onedir often report fewer detections without changing a line of code.
| What you are shipping | Build mode | What gets signed |
|---|---|---|
| A single download from your own site | onefile | The one executable |
| An installer users run once | onedir, wrapped | The installer, plus the launcher and libraries inside it |
| An internal tool under an application control policy | onedir | Every PE in the directory |
| A CLI utility distributed to developers | onefile | The one executable |
Which certificate can you actually buy in 2026?
A publicly trusted code signing certificate, either Standard (OV) or EV. As of 1 June 2023 the CA/Browser Forum requires the private key to be generated and held in hardware certified to FIPS 140-2 Level 2, Common Criteria EAL 4+ or equivalent. You receive a USB token or access to a CA-operated cloud HSM. You do not receive a PFX file, and the key cannot be exported into one.
This is the detail that makes most Python signing guides wrong rather than merely dated. Search for how to sign a PyInstaller build and the answer that comes back, in blog posts and in PyInstaller’s own wiki recipe, is some variation of signtool /f yourkey.pfx /p password app.exe. That command was correct for years. For a publicly trusted certificate issued today there is no yourkey.pfx to point it at.
Two delivery models satisfy the hardware rule, and the one you pick shapes everything downstream:
- A physical USB token shipped by the CA. The key is generated on the device. Signing needs the token plugged in, its vendor middleware installed, and an interactive desktop session.
- A CA cloud signing service, where the key lives in the CA’s HSM and a desktop client presents it to Windows as a virtual smart card. Certum’s SimplySign works this way: you authenticate with a TOTP code from a mobile app, which opens a two-hour window during which SignTool can sign as many files as you like.
A self-signed certificate is still worth having for development. It exercises the same signing path, so your build script is real before the token arrives, and it removes the warning on machines where you have installed the certificate yourself. On any other machine it changes nothing. If you have not bought yet, the Standard and EV code signing certificate options list what each level validates and how the key is delivered, which is the part that determines whether your pipeline can use it.
Where does signing fit into the build?
After PyInstaller finishes, and before anything wraps or publishes the result. Signing rewrites the file, so every step that hashes, compresses or packages the executable has to run afterwards. In practice that is three commands: build, sign with an RFC 3161 timestamp, verify. Ship an installer as well and you have a second signing pass on a second file.
pyinstaller --onefile --name app --version-file version.txt app.py
signtool sign /fd SHA256 /td SHA256 ^
/tr http://time.certum.pl ^
/a dist\app.exe
signtool verify /pa /v dist\app.exeThe flag doing the quiet work is /a. It tells SignTool to pick a suitable certificate from the Windows certificate store, which is where a token’s middleware or a cloud client’s virtual reader publishes your certificate. No file path, no password on the command line, nothing to leak into a build log. If you hold more than one code signing certificate, swap /a for /n and the exact subject name, or /sha1 and the thumbprint, so the script cannot pick the wrong one after a renewal.
/tr requests an RFC 3161 timestamp and /td SHA256 sets its digest algorithm. Use your own CA’s timestamp server where you have one. The flag-by-flag reasoning behind each of these, and what SignTool’s error codes mean when it refuses, is covered in the step-by-step SignTool guide.
One PyInstaller-specific addition is worth making while you are here. Pass --version-file and give the executable a real company name, product name and version. An unsigned binary with no version resource looks like something a script produced by accident, and the version resource is what several reputation and allowlisting systems read alongside the signature. It costs one file.
Do you have to sign the installer too?
Yes, if you ship one. Windows judges whatever the user actually double-clicks, so an unsigned Inno Setup or MSI wrapper around a perfectly signed application still produces the Unknown Publisher prompt. Sign the application first, build the installer around the already-signed file, then sign the installer. Two files, two signatures, in that order.
Getting the order backwards is a real failure mode rather than a theoretical one. If the installer is built first and the payload signed afterwards, you have signed a copy of a file that is already sitting inside the installer unsigned. Everything verifies on your machine, where you are checking dist\app.exe, and nothing verifies on the user’s, where the installer extracted its own stale copy.
Inno Setup handles this natively through its SignTool directive, which lets you define the signing command once in the setup script and have the compiler invoke it. MSI packages are signed with the same SignTool binary as an executable. In either case the release script ends with a verification pass over the finished installer, not over the file you built ten minutes earlier.
Will signing stop SmartScreen, Defender and Smart App Control?
Signing is required for all of them and finishes only one of them outright. The Unknown Publisher prompt goes away as soon as a valid signature is present. SmartScreen keeps warning until your publisher identity has accumulated download reputation. Defender scans the file regardless of who signed it. Smart App Control is the strict gate: Microsoft states that an app which is unsigned, or whose signature is invalid, is treated as untrusted and blocked.
The reputation point is where expectations usually break. A Python application rebuilt from the same source produces a different file hash every time, so nothing that keys on the hash can carry trust across a release. A signature keys on your publisher identity instead, and that identity persists across every build you ever ship with that certificate. This is exactly why a signature helps with antivirus false positives even though it does not stop a scan: it gives a vendor something durable to allowlist, and it gives you standing when you submit a false positive report.
Two things follow for anyone shipping a PyInstaller build. Sign every release with the same certificate rather than rotating between several, because splitting your output across two publisher identities splits the reputation as well. And treat antivirus detections as a separate workstream from signing: the bootloader’s history with heuristic engines is well documented, and the fixes for it are covered in why a signed EXE still gets flagged as a virus.
How do you sign in CI when the key is on a token?
You do not put the token in the build agent. A USB token needs a physically attached device, its vendor middleware, and an interactive session that a hosted runner does not have. Two arrangements work: a self-hosted Windows runner with the token attached to it, or a CA cloud signing service that hands the runner an authenticated session while the key stays in the CA’s HSM.
The self-hosted route is simpler to reason about and harder to live with. Somebody has to own a machine with a token in it, that machine has to stay logged in, and the token’s middleware generally refuses to work over a remote desktop session, which rules out the obvious way of administering it. For a team shipping a release a month it is workable. For a pipeline that signs on every merge it becomes the thing that breaks.
Cloud signing removes the physical dependency. With Certum SimplySign, a desktop client authenticated by a TOTP code opens a two-hour signing window and exposes the certificate through a virtual reader, so SignTool runs unchanged. Other CAs offer equivalent services with their own clients or PKCS#11 modules. The trade-off is which model fits your pipeline, and the comparison of cloud signing against a USB token walks through it; if you already know you need the cloud model, the code signing certificates available with cloud key delivery say which levels support it.
The 460-day clock and the timestamp that outlives it
Code signing certificates issued on or after 1 March 2026 are capped at 460 days, roughly fifteen months, under CA/Browser Forum ballot CSC-31 and version 3.10.0 of the Code Signing Baseline Requirements. Certificates issued before that date run to their original expiry. In practice your signing identity now needs renewing about once a year, and the pipeline needs a step for it.
The timestamp is what stops that clock from reaching your users. An RFC 3161 timestamp is a countersignature from a trusted authority attesting that the signature existed at a particular moment. When Windows later checks a build whose certificate has expired, it asks whether the signature was made while the certificate was valid, and a timestamp answers yes. Without one, every copy already downloaded starts failing verification on the day the certificate lapses.
Fifteen-month validity turns a missing /tr flag from a slow-burning problem into an annual one. Put the verification step in the release script, and make it check for the timestamp rather than just the signature — signtool verify /pa /v prints the timestamp information when it is there, and conspicuously does not when it is not. More on what the countersignature contains and which servers to use is in the guide to code signing timestamping.
FAQ
Working out which certificate your pipeline can use
The decision that matters for a Python build is not Standard versus EV so much as how the key reaches your signing machine. A token suits a release you cut by hand; cloud delivery suits anything a runner has to sign. My-SSL sells Certum Standard code signing from $99 a year and EV from $299 a year, with the key delivery model stated for each.
Compare Standard and EV code signing certificates