diff --git a/twine/exceptions.py b/twine/exceptions.py
index 6ced6dd..47a12ee 100644
--- a/twine/exceptions.py
+++ b/twine/exceptions.py
@@ -85,6 +85,12 @@ class InvalidSigningConfiguration(TwineException):
     pass
 
 
+class InvalidSigningExecutable(TwineException):
+    """Signing executable must be installed on system."""
+
+    pass
+
+
 class InvalidConfiguration(TwineException):
     """Raised when configuration is invalid."""
 
diff --git a/twine/package.py b/twine/package.py
index 9fe89d2..4212aae 100644
--- a/twine/package.py
+++ b/twine/package.py
@@ -33,6 +33,12 @@ from twine.wheel import Wheel
 from twine.wininst import WinInst
 from twine import exceptions
 
+try:
+    FileNotFoundError = FileNotFoundError
+except NameError:
+    FileNotFoundError = IOError  # Py2
+
+
 DIST_TYPES = {
     "bdist_wheel": Wheel,
     "bdist_wininst": WinInst,
@@ -174,10 +180,31 @@ class PackageFile(object):
         if identity:
             gpg_args += ("--local-user", identity)
         gpg_args += ("-a", self.filename)
-        subprocess.check_call(gpg_args)
+        self.run_gpg(gpg_args)
 
         self.add_gpg_signature(self.signed_filename, self.signed_basefilename)
 
+    @classmethod
+    def run_gpg(cls, gpg_args):
+        try:
+            subprocess.check_call(gpg_args)
+            return
+        except FileNotFoundError:
+            if gpg_args[0] != "gpg":
+                raise exceptions.InvalidSigningExecutable(
+                    "{} executable not available.".format(gpg_args[0]))
+
+        print("gpg executable not available. Attempting fallback to gpg2.")
+        try:
+            subprocess.check_call(("gpg2",) + gpg_args[1:])
+        except FileNotFoundError:
+            print("gpg2 executable not available.")
+            raise exceptions.InvalidSigningExecutable(
+                "'gpg' or 'gpg2' executables not available. "
+                "Try installing one of these or specifying an executable "
+                "with the --sign-with flag."
+            )
+
 
 Hexdigest = collections.namedtuple('Hexdigest', ['md5', 'sha2', 'blake2'])
 

FileNotFoundError: [Errno 2] No such file or directory: 'gpg': 'gpg'
On platform where `gpg` executable does not exist twine failsto sign with an errorl like:
```
FileNotFoundError: [Errno 2] No such file or directory: 'gpg': 'gpg'
```

This happens even if `gpg2` executable is present and working, use of gpg2 is quite common and twine should be able to prefer instead of the very old gpg.

Adding an alias does not solve the problem
diff --git a/tests/test_package.py b/tests/test_package.py
index ecb301a..8e4fbbb 100644
--- a/tests/test_package.py
+++ b/tests/test_package.py
@@ -59,6 +59,48 @@ def test_sign_file_with_identity(monkeypatch):
     assert replaced_check_call.calls == [pretend.call(args)]
 
 
+def test_run_gpg_raises_exception_if_no_gpgs(monkeypatch):
+    replaced_check_call = pretend.raiser(
+        package.FileNotFoundError('not found')
+    )
+    monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call)
+    gpg_args = ('gpg', '--detach-sign', '-a', 'pypircfile')
+
+    with pytest.raises(exceptions.InvalidSigningExecutable) as err:
+        package.PackageFile.run_gpg(gpg_args)
+
+    assert 'executables not available' in err.value.args[0]
+
+
+def test_run_gpg_raises_exception_if_not_using_gpg(monkeypatch):
+    replaced_check_call = pretend.raiser(
+        package.FileNotFoundError('not found')
+    )
+    monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call)
+    gpg_args = ('not_gpg', '--detach-sign', '-a', 'pypircfile')
+
+    with pytest.raises(exceptions.InvalidSigningExecutable) as err:
+        package.PackageFile.run_gpg(gpg_args)
+
+    assert 'not_gpg executable not available' in err.value.args[0]
+
+
+def test_run_gpg_falls_back_to_gpg2(monkeypatch):
+
+    def check_call(arg_list):
+        if arg_list[0] == 'gpg':
+            raise package.FileNotFoundError('gpg not found')
+
+    replaced_check_call = pretend.call_recorder(check_call)
+    monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call)
+    gpg_args = ('gpg', '--detach-sign', '-a', 'pypircfile')
+
+    package.PackageFile.run_gpg(gpg_args)
+
+    gpg2_args = replaced_check_call.calls[1].args
+    assert gpg2_args[0][0] == 'gpg2'
+
+
 def test_package_signed_name_is_correct():
     filename = 'tests/fixtures/deprecated-pypirc'
 

pip install -e .[keyring,with-blake2]
parse_log_pytest
requirements.txt
pytest coverage pretend check-manifest flake8 mypy doc8 sphinx sphinx-rtd-theme readme_renderer requests requests-toolbelt tqdm keyring pyblake2
apt-get update apt-get install -y gcc
3.6
docs/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs==22.2.0
Babel==2.11.0
bleach==4.1.0
build==0.9.0
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
check-manifest==0.48
colorama==0.4.5
coverage==6.2
cryptography==40.0.2
distlib==0.3.9
doc8==0.11.2
docutils==0.17.1
filelock==3.4.1
flake8==5.0.4
idna==3.10
imagesize==1.4.1
importlib-metadata==4.2.0
importlib-resources==5.4.0
iniconfig==1.1.1
jeepney==0.7.1
Jinja2==3.0.3
keyring==23.4.1
MarkupSafe==2.0.1
mccabe==0.7.0
mypy==0.971
mypy-extensions==1.0.0
packaging==21.3
pbr==6.1.1
pep517==0.13.1
pkginfo==1.10.0
platformdirs==2.4.0
pluggy==1.0.0
pretend==1.0.9
py==1.11.0
pyblake2==1.1.2
pycodestyle==2.9.1
pycparser==2.21
pyflakes==2.5.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
pytz==2025.2
readme-renderer==34.0
releases==2.1.1
requests==2.27.1
requests-toolbelt==1.0.0
restructuredtext-lint==1.4.0
rfc3986==1.5.0
SecretStorage==3.3.3
semantic-version==2.6.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==4.3.2
sphinx-rtd-theme==1.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
stevedore==3.5.2
toml==0.10.2
tomli==1.2.3
tox==3.28.0
tqdm==4.64.1
-e git+https://github.com/pypa/twine.git@579f3fe60f2333972ba0260f44033ee1889ca3ca#egg=twine
typed-ast==1.5.5
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.17.1
webencodings==0.5.1
zipp==3.6.0

name: twine
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - bleach==4.1.0
      - build==0.9.0
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - check-manifest==0.48
      - colorama==0.4.5
      - coverage==6.2
      - cryptography==40.0.2
      - distlib==0.3.9
      - doc8==0.11.2
      - docutils==0.17.1
      - filelock==3.4.1
      - flake8==5.0.4
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.2.0
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - jeepney==0.7.1
      - jinja2==3.0.3
      - keyring==23.4.1
      - markupsafe==2.0.1
      - mccabe==0.7.0
      - mypy==0.971
      - mypy-extensions==1.0.0
      - packaging==21.3
      - pbr==6.1.1
      - pep517==0.13.1
      - pkginfo==1.10.0
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pretend==1.0.9
      - py==1.11.0
      - pyblake2==1.1.2
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pyflakes==2.5.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytz==2025.2
      - readme-renderer==34.0
      - releases==2.1.1
      - requests==2.27.1
      - requests-toolbelt==1.0.0
      - restructuredtext-lint==1.4.0
      - rfc3986==1.5.0
      - secretstorage==3.3.3
      - semantic-version==2.6.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==4.3.2
      - sphinx-rtd-theme==1.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - stevedore==3.5.2
      - toml==0.10.2
      - tomli==1.2.3
      - tox==3.28.0
      - tqdm==4.64.1
      - typed-ast==1.5.5
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.17.1
      - webencodings==0.5.1
      - zipp==3.6.0
prefix: /opt/conda/envs/twine

tests/test_package.py::test_run_gpg_raises_exception_if_no_gpgs tests/test_package.py::test_run_gpg_raises_exception_if_not_using_gpg tests/test_package.py::test_run_gpg_falls_back_to_gpg2
tests/test_package.py::test_sign_file tests/test_package.py::test_sign_file_with_identity tests/test_package.py::test_package_signed_name_is_correct tests/test_package.py::test_metadata_dictionary[None] tests/test_package.py::test_metadata_dictionary[gpg_signature1] tests/test_package.py::test_hash_manager tests/test_package.py::test_fips_hash_manager tests/test_package.py::test_no_blake2_hash_manager tests/test_package.py::test_pkginfo_returns_no_metadata
twine/exceptions.py twine/package.py
twine/exceptions.py twine/package.py