Skip to content

Code Signing

This guide covers how to sign your Wails applications for macOS, Windows, and Linux. Wails v3 provides built-in CLI tools for code signing, notarization, and PGP key management.

  • macOS - Sign and notarize your macOS applications
  • Windows - Sign your Windows executables and packages
  • Linux - Sign DEB and RPM packages with PGP keys

This matrix shows what you can sign from each source platform:

Target FormatFrom WindowsFrom macOSFrom Linux
Windows EXE/MSI
macOS .app bundle
macOS notarization
Linux DEB
Linux RPM

Wails automatically selects the best available signing backend:

PlatformNative BackendCross-Platform Backend
Windowssigntool.exe (Windows SDK)Built-in
macOScodesign (Xcode)Not available
LinuxN/ABuilt-in

When running on the native platform, Wails uses the native tools for maximum compatibility. When cross-compiling, it uses the built-in signing support.

The fastest way to configure signing is the setup wizard:

Terminal window
wails3 setup

This writes a shared signing configuration to ~/.config/wails/defaults.yaml that is honored at sign time on every platform (see Configuration precedence). Its signing step:

  • Detects which signing tools are installed for the platform you want to target — from any host — and shows the install command for your OS (e.g. brew install gnupg, sudo apt install osslsigncode, winget install GnuPG.Gpg4win).
  • On macOS, lists Developer ID certificates from your keychain.
  • For Linux, lists your GPG keys and can generate and export a new one.
  • For Windows, can generate a self-signed certificate (for testing) via OpenSSL.
  • Stores passwords securely in your system keychain (not in Taskfiles).

When you run a sign task, each signing option is resolved in this order (first match wins):

  1. An explicit flag passed to wails3 tool sign (e.g. --pgp-key, --certificate, --identity).
  2. The matching project Taskfile var (PGP_KEY, SIGN_CERTIFICATE/SIGN_THUMBPRINT, SIGN_IDENTITY, …).
  3. The global config in ~/.config/wails/defaults.yaml (written by wails3 setup).

This means the Taskfile vars are optional overrides: if a var is unset, the globally configured key/certificate/identity is used. If none of the three provides a value, the sign command reports a clear error telling you how to configure it.

To configure signing for a single project rather than globally, run the per-project wizard from inside the project — it writes the vars into that project’s build/<platform>/Taskfile.yml files:

Terminal window
wails3 setup signing # all detected platforms
wails3 setup signing --platform windows --platform linux

Alternatively, you can manually edit the platform-specific Taskfiles. Edit the vars section at the top of each file:

Edit build/darwin/Taskfile.yml:

vars:
SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)"
KEYCHAIN_PROFILE: "my-notarize-profile"
# ENTITLEMENTS: "build/darwin/entitlements.plist"

Then run:

Terminal window
wails3 task darwin:sign # Sign only
wails3 task darwin:sign:notarize # Sign and notarize

You can also inspect signing state from the system directly:

Terminal window
# List available macOS code-signing identities
security find-identity -v -p codesigning
# List PGP keys (Linux package signing)
gpg --list-keys

For interactive setup of every platform’s signing configuration, use the wizard:

Terminal window
wails3 setup signing
  • Apple Developer Account ($99/year)
  • Developer ID Application certificate
  • Xcode Command Line Tools installed

Check available signing identities:

Terminal window
security find-identity -v -p codesigning

Output:

Found 2 signing identities:
Developer ID Application: Your Company (ABCD1234) [valid]
Hash: ABC123DEF456...
Apple Development: your@email.com (XYZ789) [valid]
Hash: DEF789ABC123...

Edit build/darwin/Taskfile.yml and set the signing variables:

vars:
SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)"
KEYCHAIN_PROFILE: "my-notarize-profile"
ENTITLEMENTS: "build/darwin/entitlements.plist"
VariableRequiredDescription
SIGN_IDENTITYYesYour Developer ID (e.g., “Developer ID Application: Your Company (TEAMID)“)
KEYCHAIN_PROFILEFor notarizationKeychain profile name with stored credentials
ENTITLEMENTSNoPath to entitlements file

Then run:

Terminal window
wails3 task darwin:sign # Build, package, and sign
wails3 task darwin:sign:notarize # Build, package, sign, and notarize

Entitlements control what capabilities your app has access to. Wails apps typically need different entitlements for development vs production:

  • Development: Requires JIT, unsigned memory, and debugging entitlements
  • Production: Minimal entitlements (just network access)

Use the interactive setup wizard to generate both files:

Terminal window
wails3 setup entitlements

This creates:

  • build/darwin/entitlements.dev.plist - For development builds
  • build/darwin/entitlements.plist - For production/signed builds

Presets available:

PresetDescription
DevelopmentJIT, unsigned memory, debugging, network
ProductionNetwork only (minimal, most secure)
BothCreates both dev and production files (recommended)
App StoreSandbox enabled with network and file access
CustomChoose individual entitlements

Then set ENTITLEMENTS in your Taskfile vars to point to the appropriate file.

Apple requires all distributed apps to be notarized.

  1. Store your credentials in the keychain (one-time setup). Either run wails3 setup signing (it prompts for these values and calls notarytool under the hood) or call notarytool directly:

    Terminal window
    xcrun notarytool store-credentials "my-notarize-profile" \
    --apple-id "your@email.com" \
    --team-id "ABCD1234" \
    --password "app-specific-password"
  2. Set KEYCHAIN_PROFILE in your Taskfile to match the profile name above.

  3. Sign and notarize your app:

    Terminal window
    wails3 task darwin:sign:notarize
  4. Verify notarization:

    Terminal window
    spctl --assess --verbose=2 bin/MyApp.app
  • Code signing certificate (from DigiCert, Sectigo, etc.)
  • For native signing on Windows: Windows SDK installed (for signtool.exe)
  • For cross-platform signing from macOS/Linux: osslsigncode (the wails3 setup signing step shows the host-specific install command)

Generating a self-signed certificate (testing)

Section titled “Generating a self-signed certificate (testing)”

If you just need to test the signing pipeline, run wails3 setup, open the Windows tab of the signing step, and choose Generate a self-signed certificate. This uses OpenSSL to create a code-signing .pfx and records its path in the global config.

Edit build/windows/Taskfile.yml and set the signing variables:

vars:
SIGN_CERTIFICATE: "path/to/certificate.pfx"
# Or use thumbprint instead:
# SIGN_THUMBPRINT: "certificate-thumbprint"
# TIMESTAMP_SERVER: "http://timestamp.digicert.com"
VariableRequiredDescription
SIGN_CERTIFICATEOverridePath to .pfx/.p12 certificate file (falls back to global config if unset)
SIGN_THUMBPRINTOverrideCertificate thumbprint in Windows cert store (alternative to SIGN_CERTIFICATE)
TIMESTAMP_SERVERNoTimestamp server URL (default: http://timestamp.digicert.com)

Then run:

Terminal window
wails3 task windows:sign # Build and sign executable
wails3 task windows:sign:installer # Build and sign NSIS installer

Windows executables can be signed from any platform. The same Taskfile configuration and commands work on macOS and Linux.

FormatExtensionNotes
Executables.exeStandard PE signing
Installers.msiWindows Installer packages
App Packages.msix, .appxModern Windows apps

Linux packages (DEB and RPM) are signed using PGP/GPG keys. Unlike Windows and macOS code signing, Linux package signing proves the package came from a trusted source rather than that the code is trusted by the OS.

  • PGP key pair (can be generated with Wails)

The easiest way is the setup wizard — run wails3 setup, open the Linux tab of the signing step, and choose Create a new GPG key. The wizard:

  • generates an RSA 4096 key in your GPG keyring (leave the passphrase blank for an unattended, CI-friendly key),
  • exports it to ~/.wails/signing/<keyid>.asc (the file a build signs with), and
  • records both the key ID and the exported path in ~/.config/wails/defaults.yaml, so it’s used automatically at sign time.

You can also do it by hand with gpg:

Terminal window
# Interactive — the wizard will prompt for name, email, key size and expiry.
gpg --full-generate-key
# Export the key pair to ASCII-armoured files for the Taskfile to consume.
gpg --armor --export-secret-keys "your@email.com" > signing-key.asc
gpg --armor --export "your@email.com" > signing-key.pub.asc

Recommended settings: RSA 4096-bit, 1-year expiry, protected with a strong password.

Edit build/linux/Taskfile.yml and set the signing variables:

vars:
PGP_KEY: "path/to/signing-key.asc"
# SIGN_ROLE: "builder" # Options: origin, maint, archive, builder
VariableRequiredDescription
PGP_KEYOverridePath to the exported PGP private key file (falls back to the key configured globally via wails3 setup if unset)
SIGN_ROLENoDEB signing role (default: builder)

Then run:

Terminal window
wails3 task linux:sign:deb # Build and sign DEB package
wails3 task linux:sign:rpm # Build and sign RPM package
wails3 task linux:sign:packages # Build and sign all packages

For DEB packages, you can specify the signing role via SIGN_ROLE:

  • origin: Signature from the package origin
  • maint: Signature from the package maintainer
  • archive: Signature from the archive maintainer
  • builder: Signature from the package builder (default)

Linux packages can be signed from any platform. The same Taskfile configuration and commands work on Windows and macOS.

Terminal window
gpg --show-keys signing-key.asc

Output:

pub rsa4096 2024-01-15 [SC] [expires: 2025-01-15]
1234 5678 90AB CDEF 1234 5678 90AB CDEF 1234 5678
uid Your Name <your@email.com>
Terminal window
# Verify DEB signature
dpkg-sig --verify myapp_1.0.0_amd64.deb
# Verify RPM signature
rpm --checksig myapp-1.0.0.x86_64.rpm

Users need your public key to verify packages:

Terminal window
# Export public key for distribution
gpg --armor --export "your@email.com" > myapp-signing.pub.asc
# Users can import it:
# For DEB (apt):
sudo apt-key add myapp-signing.pub.asc
# Or for modern apt:
sudo cp myapp-signing.pub.asc /etc/apt/trusted.gpg.d/
# For RPM:
sudo rpm --import myapp-signing.pub.asc

In CI environments, passwords are provided via environment variables instead of the system keychain:

Environment VariableDescription
WAILS_WINDOWS_CERT_PASSWORDWindows certificate password
WAILS_PGP_PASSWORDPGP key password for Linux packages

You can also pass Taskfile variables directly:

Terminal window
wails3 task darwin:sign SIGN_IDENTITY="$SIGN_IDENTITY" KEYCHAIN_PROFILE="$KEYCHAIN_PROFILE"
name: Build and Sign macOS
on:
push:
tags: ['v*']
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install Wails
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Import Certificate
env:
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
run: |
echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain
- name: Store Notarization Credentials
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: |
xcrun notarytool store-credentials "notarize-profile" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD"
- name: Build, Sign, and Notarize
env:
SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
run: |
wails3 task darwin:sign:notarize \
SIGN_IDENTITY="$SIGN_IDENTITY" \
KEYCHAIN_PROFILE="notarize-profile"
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-macOS
path: bin/*.app
name: Build and Sign Windows
on:
push:
tags: ['v*']
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install Wails
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Import Certificate
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE }}
run: |
$certBytes = [Convert]::FromBase64String($env:CERTIFICATE_BASE64)
[IO.File]::WriteAllBytes("certificate.pfx", $certBytes)
- name: Build and Sign
env:
WAILS_WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
wails3 task windows:sign SIGN_CERTIFICATE=certificate.pfx
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-Windows
path: bin/*.exe

Sign Windows and Linux packages from a single Linux runner:

name: Build and Sign (Cross-Platform)
on:
push:
tags: ['v*']
jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install Wails
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Install Build Dependencies
run: |
sudo apt-get update
sudo apt-get install -y nsis rpm
# Import certificates
- name: Import Certificates
env:
WINDOWS_CERT_BASE64: ${{ secrets.WINDOWS_CERTIFICATE }}
PGP_KEY_BASE64: ${{ secrets.PGP_PRIVATE_KEY }}
run: |
echo "$WINDOWS_CERT_BASE64" | base64 -d > certificate.pfx
echo "$PGP_KEY_BASE64" | base64 -d > signing-key.asc
# Build and sign Windows
- name: Build and Sign Windows
env:
WAILS_WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
wails3 task windows:sign SIGN_CERTIFICATE=certificate.pfx
# Build and sign Linux packages
- name: Build and Sign Linux Packages
env:
WAILS_PGP_PASSWORD: ${{ secrets.PGP_PASSWORD }}
run: |
wails3 task linux:sign:packages PGP_KEY=signing-key.asc
# Cleanup secrets
- name: Cleanup
if: always()
run: rm -f certificate.pfx signing-key.asc
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: signed-binaries
path: |
bin/*.exe
bin/*.deb
bin/*.rpm

Interactive wizard to configure signing for your project.

Terminal window
wails3 setup signing [flags]
Flags:
--platform Platform to configure (darwin, windows, linux). Repeatable.
If omitted, auto-detects which platforms to configure from the build directory.

The wizard guides you through:

  • macOS: Selecting a Developer ID certificate, configuring notarization credentials (calls xcrun notarytool store-credentials).
  • Windows: Choosing between certificate file or thumbprint, setting password and timestamp server.
  • Linux: Using an existing PGP key or generating a new one (calls gpg), configuring signing role.

Interactive wizard to configure macOS entitlements.

Terminal window
wails3 setup entitlements [flags]
Flags:
--output Output path for entitlements.plist (default: build/darwin/entitlements.plist)

Presets:

  • Development: Creates entitlements.dev.plist with JIT, debugging, and network
  • Production: Creates entitlements.plist with minimal entitlements
  • Both: Creates both files (recommended)
  • App Store: Creates sandboxed entitlements for Mac App Store
  • Custom: Choose individual entitlements and target file

Sign binaries and packages for the current or specified platform. This is a wrapper that calls the appropriate platform-specific signing task.

Terminal window
wails3 sign
wails3 sign GOOS=darwin
wails3 sign GOOS=windows
wails3 sign GOOS=linux

This runs the corresponding <platform>:sign task which uses the signing configuration from your Taskfile.

Low-level command to sign a specific file directly. Used internally by the Taskfiles.

Terminal window
wails3 tool sign [flags]

Common Flags:

FlagDescription
--inputPath to the file to sign
--outputOutput path (optional, defaults to in-place)
--verboseEnable verbose output

Windows/macOS Flags:

FlagDescription
--certificatePath to PKCS#12 (.pfx/.p12) certificate
--passwordCertificate password
--timestampTimestamp server URL

macOS-Specific Flags:

FlagDescription
--identitySigning identity (use ’-’ for ad-hoc)
--entitlementsPath to entitlements plist
--hardened-runtimeEnable hardened runtime (default: true)
--notarizeSubmit for notarization
--keychain-profileKeychain profile for notarization

Windows-Specific Flags:

FlagDescription
--thumbprintCertificate thumbprint in Windows store

Linux-Specific Flags:

FlagDescription
--pgp-keyPath to PGP private key
--pgp-passwordPGP key password
--roleDEB signing role (origin/maint/archive/builder)

There is no wails3 signing command in v3. To inspect signing state, use the native tooling directly:

TaskCommand
List macOS code-signing identitiessecurity find-identity -v -p codesigning
Store notarization credentialsxcrun notarytool store-credentials "<profile>" --apple-id … --team-id … --password …
Inspect a PGP key filegpg --show-keys <key.asc>
Generate a PGP key pairgpg --full-generate-key
Export a public keygpg --armor --export <email>

“No Developer ID certificate found”

  • Ensure your certificate is installed in the Keychain
  • Check it hasn’t expired with security find-identity -v -p codesigning
  • Make sure you have a “Developer ID Application” certificate (not just “Apple Development”)

“Notarization failed”

  • Check the notarization log: xcrun notarytool log <submission-id> --keychain-profile <profile>
  • Ensure hardened runtime is enabled
  • Verify your app doesn’t include unsigned binaries

“Codesign failed”

  • Make sure the keychain is unlocked: security unlock-keychain
  • Check file permissions on the app bundle

“Certificate not found”

  • Verify the certificate path is correct
  • Check the certificate password
  • Ensure the certificate is valid (not expired or revoked)

“Timestamp server error”

  • Try a different timestamp server:
    • http://timestamp.digicert.com
    • http://timestamp.sectigo.com
    • http://timestamp.comodoca.com

“Invalid PGP key”

  • Ensure the key file is in ASCII-armored format
  • Check the key hasn’t expired with gpg --show-keys <key.asc>
  • Verify the password is correct

“Signature verification failed”

  • Ensure the public key is properly imported
  • Check that the package wasn’t modified after signing