Skip to main content
    Code Signing

    How to Sign a Python EXE Built with PyInstaller

    Sign a PyInstaller .exe on Windows in 2026: why PFX files are gone, what a onefile signature really covers, and where signing belongs in the build.

    MS
    My-SSL Team
    ·
    14 min read
    ·
    Published August 29, 2026
    ·
    Last updated August 29, 2026

    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.

    What an Authenticode signature covers in a PyInstaller onefile buildThe file you ship is a single executable containing the PyInstaller bootloader stub, a compressed archive of the Python runtime and your code, and the Authenticode signature appended at the end. Your signature covers that whole file, and it is the file Windows checks when the user double-clicks it. When the program starts, the bootloader unpacks the Python interpreter DLL, the extension modules, the standard library archive and your data files into a temporary directory named underscore M E I followed by random characters. Those unpacked files are written at run time, so they are outside the signed boundary and carry no signature of yours, no matter how the executable was signed.Your signature covers the file you ship, not what it unpacksapp.exe — signed, one PE filePyInstaller bootloader stubCompressed archive (CArchive)Authenticode signature + timestampWindows checks this when the user runs itstart-up%TEMP%\_MEIxxxxxx\python3xx.dll_ssl.pyd, _socket.pyd, ...base_library.zipyour bundled data fileswritten at run timeoutside the signed boundaryno signature of yours reaches theseA onefile build gives you exactly one signable artefact. Choosing onedirmakes the runtime files signable, because each exists on disk first.
    The practical consequence: if someone asks you to prove every binary in your application is signed, no amount of care with the signing command will get you there from a onefile build. That is a packaging decision made earlier.

    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.

    Onefile and onedir PyInstaller builds compared as signing targetsA onefile build produces one executable, needs one SignTool call, and the files unpacked at run time cannot be signed. It is the right default for a consumer download. A onedir build produces a launcher plus a directory of dynamic libraries, needs one SignTool call per binary you choose to cover, and every one of those files can carry your signature. It starts faster because nothing is unpacked at launch, and it is the mode to choose when an enterprise application control policy or a security review needs the whole tree signed.Build mode is a signing decision--onefileArtefacts to ship: 1SignTool calls: 1Runtime DLLs signable: noStart-up: unpacks to %TEMP% firstHeuristic AV attention: higherPick for: a download from your siteor a release asset users grab directly--onedirArtefacts to ship: a directorySignTool calls: one per binaryRuntime DLLs signable: yesStart-up: nothing to unpackHeuristic AV attention: lowerPick for: an installer payload, oranywhere the whole tree gets inspected
    Most teams that end up shipping an installer discover they wanted onedir all along: the installer becomes the single signed thing the user double-clicks, and the payload underneath it is fully signable.

    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 shippingBuild modeWhat gets signed
    A single download from your own siteonefileThe one executable
    An installer users run onceonedir, wrappedThe installer, plus the launcher and libraries inside it
    An internal tool under an application control policyonedirEvery PE in the directory
    A CLI utility distributed to developersonefileThe 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.

    Where the two signing steps sit in a PyInstaller release pipelineThe order is fixed by the fact that signing rewrites the file. First PyInstaller builds the executable. Then the executable is signed and timestamped, which is the first signing step. Then the signature is verified. Then the installer is built around the already signed executable. Then the installer itself is signed and timestamped, which is the second signing step. Only then is the release published. Any step that hashes, compresses or wraps a file must come after that file has been signed, because signing changes its bytes.Signing rewrites the file, so it goes before anything that wraps itpyinstallerbuildsign app.exe+ timestampbuild theinstallersign installer+ timestampverify with signtool verify /pa /vverify again before publishingSkip the second box and the installer shows Unknown Publisher, even thoughthe app inside it is signed. Reverse the order and you package a stale copy.
    The single most common mistake in a Python release pipeline is publishing the wrapper unsigned, because the wrapper is usually added last and the signing script was written before it existed.
    Build, sign, verify
    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.exe

    The 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.

    What a signature does at each of the three Windows gatesThree separate Windows mechanisms judge a downloaded application. The User Account Control prompt is the one a signature fixes outright: a valid signature replaces the Unknown Publisher text with your verified organisation name. SmartScreen is only partly addressed by a signature, because its warning clears as download reputation accrues to your publisher identity over time rather than at the moment you sign. Microsoft Defender is not addressed by a signature at all, since it scans behaviour and content, though a stable publisher identity gives vendors something durable to allowlist. Smart App Control, highlighted, is the strict gate: Microsoft states that an app which is unsigned or whose signature is invalid is treated as untrusted and blocked, with no run anyway option.One signature, four gates, four different outcomesUAC promptSolved by signingUnknown Publisherbecomes yourorganisation nameSmartScreenHelped over timeReputation attachesto the publisher,not to each buildDefenderNot solved by signingScans content andbehaviour regardlessof who signed itSmart App ControlSigning is requiredUnsigned or invalidcounts as untrustedand is blockedSigning is the entry ticket to all four. It settles the first one outright,and starts a clock on the second.
    Expectation management matters here: a first-time publisher who signs on Monday and still sees a SmartScreen warning on Tuesday has not been sold a broken certificate.

    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.

    How an RFC 3161 timestamp keeps a release valid past the 460-day certificate expiryA code signing certificate issued on or after 1 March 2026 is valid for at most 460 days under CA/Browser Forum ballot CSC-31. Two builds are signed during that window. The build signed with a timestamp keeps verifying indefinitely after the certificate expires, because the timestamp proves the signature was made while the certificate was still valid. The build signed without a timestamp stops verifying on the day the certificate expires, and every copy already downloaded by users starts showing an invalid signature at that moment.The timestamp is what your shipped builds depend onissuedexpires (460 days max)laterBuild A — signed with /tr timestampstill verifies years from nowBuild B — signed without a timestampinvalid the day the certificate expiresstops verifying here
    Build B is not a hypothetical. It is what happens when a release script omits one flag, and nobody notices for fifteen months, by which point the broken copies are already on users’ disks.

    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

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.

    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