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 easiest way to configure signing is using the interactive setup wizard:

Terminal window
wails3 setup signing

This command:

  • Walks you through configuring signing credentials for each platform
  • On macOS, lists available Developer ID certificates from your keychain
  • For Linux, can generate a new PGP key if you don’t have one
  • Stores passwords securely in your system keychain (not in Taskfiles)
  • Updates the vars section in each platform’s Taskfile with non-sensitive config

To configure only specific platforms:

Terminal window
wails3 setup signing --platform darwin
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 use the CLI directly:

Terminal window
# Check signing capabilities on your system
wails3 signing info
# List available signing identities (macOS/Windows)
wails3 signing list
  • Apple Developer Account ($99/year)
  • Developer ID Application certificate
  • Xcode Command Line Tools installed

Check available signing identities:

Terminal window
wails3 signing list

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):

    Terminal window
    wails3 signing credentials \
    --apple-id "your@email.com" \
    --team-id "ABCD1234" \
    --password "app-specific-password" \
    --profile "my-notarize-profile"
  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: Windows SDK installed (for signtool.exe)
  • For cross-platform: Just the certificate file

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_CERTIFICATEOne of thesePath to .pfx/.p12 certificate file
SIGN_THUMBPRINTOne of theseCertificate thumbprint in Windows cert store
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)

If you don’t have a PGP key, Wails can generate one for you:

Terminal window
wails3 signing generate-key \
--name "Your Name" \
--email "your@email.com" \
--comment "Package Signing Key" \
--output-private signing-key.asc \
--output-public signing-key.pub.asc

Options:

  • --bits: Key size (default: 4096)
  • --expiry: Key expiry duration (e.g., “1y”, “6m”, “0” for no expiry)
  • --password: Password to protect the private key

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_KEYYesPath to PGP private key file
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
wails3 signing key-info --key signing-key.asc

Output:

PGP Key Information:
Key ID: ABC123DEF456
Fingerprint: 1234 5678 90AB CDEF ...
User IDs: Your Name <your@email.com>
Created: 2024-01-15
Expires: 2025-01-15
Has Private: Yes
Encrypted: Yes
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
wails3 signing key-info --key signing-key.asc --export-public > 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: |
wails3 signing credentials \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD" \
--profile "notarize-profile"
- 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(s) to configure (darwin, windows, linux)
If not specified, configures all platforms

The wizard guides you through:

  • macOS: Selecting a Developer ID certificate, configuring notarization profile
  • Windows: Choosing between certificate file or thumbprint, setting password and timestamp server
  • Linux: Using an existing PGP key or generating a new one, configuring signing role

Interactive wizard to configure macOS entitlements.

Terminal window
wails3 setup entitlements [flags]
Flags:
--output Output directory (default: build/darwin)

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
--descriptionApplication description
--urlApplication URL

Linux-Specific Flags:

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

Display signing capabilities on the current system.

Terminal window
wails3 signing info

List available signing identities.

Terminal window
wails3 signing list

Store notarization credentials in keychain (macOS only).

Terminal window
wails3 signing credentials [flags]
Flags:
--apple-id Apple ID email
--team-id Apple Developer Team ID
--password App-specific password
--profile Keychain profile name

Generate a PGP key pair for Linux package signing.

Terminal window
wails3 signing generate-key [flags]
Flags:
--name Name for the key (required)
--email Email for the key (required)
--comment Comment for the key
--bits Key size in bits (default: 4096)
--expiry Key expiry (e.g., "1y", "6m", "0" for never)
--password Password to encrypt private key
--output-private Path for private key output
--output-public Path for public key output

Display information about a PGP key.

Terminal window
wails3 signing key-info --key <path-to-key>

“No Developer ID certificate found”

  • Ensure your certificate is installed in the Keychain
  • Check it hasn’t expired with wails3 signing list
  • 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 wails3 signing key-info
  • Verify the password is correct

“Signature verification failed”

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