Publishing to NuGet.org from GitHub Actions with Trusted Publishing

TL;DR: If you publish NuGet packages from GitHub Actions using a long-lived NUGET_API_KEY secret, that workflow is going to break. Long-lived API keys are being deprecated, and existing keys are scheduled to stop working in late 2026. The new process is Trusted Publishing: your workflow uses GitHub’s OIDC token to request a short-lived, single-use API key at publish time, so you never store a key in GitHub again. This post walks through the migration.

Note on dates: The specific deprecation dates below are summarized from announcements that were current at the time of writing (9/8/2026). Always confirm the exact dates and behavior against the official NuGet and Microsoft sources linked at the end before you rely on them, since timelines can change.

Why this is changing

Trusted Publishing is now the recommended and supported way to publish to NuGet.org from GitHub Actions. Instead of storing a long-lived API key in GitHub Secrets, your workflow uses GitHub’s OIDC token to request a temporary, single-use NuGet API key that expires in about an hour.

This is part of a broader industry move toward short-lived, token-based credentials in CI/CD pipelines. After several high-profile package supply-chain attacks, minimizing the blast radius of a leaked secret matters: a short-lived token expires quickly and cannot be reused, so a compromised log or artifact is far less dangerous than a leaked long-lived key.

What is being deprecated

Long-lived API keys are being phased out. Based on the announcements available at the time of writing:

  • New API keys created after the cutover date are limited to a short lifetime (reported as 30 days).
  • Existing long-lived API keys are scheduled to stop working in late 2026.

The practical takeaway: if your GitHub Actions workflow depends on a static NUGET_API_KEY secret, it will stop working once that key expires. Migrating to Trusted Publishing removes that dependency entirely.

Timeline summary

ChangeReported timing
New API keys limited to a short lifetimeAug 17, 2026
Existing long-lived API keys stop workingNov 1, 2026
Trusted Publishing availableAlready live

Treat the dates as approximate and confirm them against the primary sources linked below. Microsoft may adjust the timeline, and the exact behavior of existing keys as the deadline looms closer.

Should you migrate now?

Yes. Trusted Publishing is already fully supported, and the long-lived key model is being sunset. If you maintain multiple packages, this also removes the recurring pain of rotating and re-distributing keys. Migrating early means your pipelines keep working without interruption when the old keys are deprecated.

Step 1: Configure a Trusted Publisher on NuGet.org

Before changing your workflow, register the publisher on NuGet.org:

  1. Go to Account Settings → Trusted Publishers.
  2. Add a new GitHub publisher.
  3. Select your repository.
  4. Choose the package IDs you want to allow publishing.
  5. Save.

You configure a separate trusted publisher entry for each GitHub repository you publish from, but you can use the same NuGet.org account across all of them.

Step 2: Update your GitHub Actions workflow

The migration comes down to four changes:

  • Add permissions: id-token: write so the job can request an OIDC token.
  • Remove the static NUGET_API_KEY secret usage.
  • Add the NuGet/login step to exchange the OIDC token for a short-lived API key.
  • Use the step’s NUGET_API_KEY output when pushing.

Before and after

The key diff in the publish step looks like this:

+permissions:
+ contents: read
+ id-token: write # REQUIRED for OIDC -> NuGet temporary API key
- - name: Publish packages
- run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
+
+ - name: NuGet login (OIDC -> temporary API key)
+ id: login
+ uses: NuGet/login@v1
+ with:
+ user: your-nuget-username
+
+ - name: Publish packages
+ run: >
+ dotnet nuget push "./nupkgs/*.nupkg"
+ --api-key ${{ steps.login.outputs.NUGET_API_KEY }}
+ --source https://api.nuget.org/v3/index.json
+ --skip-duplicate

Note that the secrets.NUGET_API_KEY reference disappears entirely; there is no key left to store or rotate.

Full workflow (drop-in replacement)

name: Publish NuGet Packages

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: read
  id-token: write   # REQUIRED for OIDC -> NuGet temporary API key

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release --no-restore

      - name: Pack
        run: dotnet pack --configuration Release --no-build --output ./nupkgs

      # Exchange the GitHub OIDC token for a short-lived NuGet API key.
      - name: NuGet login (OIDC -> temporary API key)
        id: login
        uses: NuGet/login@v1
        with:
          user: your-nuget-username

      - name: Publish packages
        run: >
          dotnet nuget push "./nupkgs/*.nupkg"
          --api-key ${{ steps.login.outputs.NUGET_API_KEY }}
          --source https://api.nuget.org/v3/index.json
          --skip-duplicate

A couple of practical notes on the push step:

  • The > YAML block scalar keeps the multi-line command robust across shells; avoid raw backslash line-continuations inside a run: string.
  • --skip-duplicate lets re-runs succeed when a version is already published, instead of failing the whole job.

Troubleshooting

If the publish step fails after migrating, check these first:

  • Missing OIDC permission: Confirm permissions: id-token: write is present at the job or workflow level. Without it, the OIDC token request fails.
  • Trusted publisher not configured: The repository (and package IDs) must be registered under Trusted Publishers on NuGet.org, and must match the repo running the workflow.
  • Package ID mismatch: The trusted publisher entry must allow the exact package IDs you are pushing. A new package ID needs to be added before its first publish.
  • Username input: The user value must match your NuGet.org account username.
  • Fork or pull-request runs: OIDC-based publishing runs from your repository context; workflows triggered from forks generally will not have access to the token.

Wrapping up

Trusted Publishing removes the last long-lived secret from your NuGet publishing pipeline. Once the trusted publisher is configured on NuGet.org and your workflow requests a short-lived key via OIDC, there is nothing left to store or rotate in GitHub. For maintainers of package families – for example, a core library plus companion packages – this is a meaningful reduction in credential management overhead, and it future-proofs your pipeline against the deprecation of long-lived keys.

Sources

Leave a comment