twine "403 Forbidden" uploading to a private PyPI in CI
twine got 403 uploading to the private index. Either no credentials reached the server, or the authenticated account is not allowed to upload to that project. 403 will not clear on retry.
What this error means
twine upload fails with "HTTPError: 403 Forbidden from https://<host>/ Invalid or non-existent authentication information" or a permission message for the project.
Uploading distributions to https://pypi.internal.example.com/
Uploading internal_lib-1.1.0-py3-none-any.whl
HTTPError: 403 Forbidden from https://pypi.internal.example.com/
Invalid or non-existent authentication information.Common causes
Missing or wrong upload credentials
twine sent no username/password (or __token__ + API token), or the values are wrong, so the server forbids the upload.
The account cannot upload to that project
The credentials authenticate but the user is not an owner/maintainer of the project on the private index.
How to fix it
Supply token credentials via environment
- Set
TWINE_USERNAME=__token__andTWINE_PASSWORDto the API token from a secret. - Point
TWINE_REPOSITORY_URLat the private index. - Re-run the upload.
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
TWINE_REPOSITORY_URL: https://pypi.internal.example.com/Confirm upload permission for the project
A persistent 403 with valid auth means the account is not a maintainer of the project; grant it on the index.
How to prevent it
- Use API tokens with
__token__rather than passwords. - Keep credentials in CI secrets and pass them via TWINE_* env.
- Grant the CI account maintainer rights on the target project.