Web API  

NuGet API Key Rotation: Automating 30-Day Credential Renewal in GitHub Actions

Introduction

Publishing a .NET package from GitHub Actions often starts with a simple setup: create a NuGet API key, save it as a repository secret, and use it during dotnet nuget push.

The problem appears later.

The key expires. A package release starts. The workflow reaches the publishing step and fails with an authentication error.

Now someone has to create another key, update the secret, verify the workflow, and repeat the process later.

A better design is to make credential lifetime part of the CI/CD architecture.

NuGet supports scoped API keys with package and permission restrictions, while its newer Trusted Publishing capability can eliminate long-lived API keys entirely by using GitHub Actions OIDC and short-lived credentials.

This article focuses on the traditional API-key approach and shows how to automate a 30-day rotation process in GitHub Actions, while also explaining why Trusted Publishing is a better long-term option when it is available.

Why API Key Rotation Matters

A NuGet API key is effectively a publishing credential.

If that credential is exposed, someone may be able to publish packages using the permissions assigned to it. NuGet explicitly recommends treating API keys as secrets and deleting or regenerating them if they are accidentally exposed.

A typical workflow looks like this:

Developer
   |
   v
Git Push / Release
   |
   v
GitHub Actions
   |
   v
NuGet API Key
   |
   v
dotnet nuget push
   |
   v
nuget.org

The weak point is the static credential:

GitHub Secret
     |
     v
Long-lived API Key

A rotation strategy reduces the lifetime of that credential.

Current Key
    |
    v
Rotate
    |
    v
New Key
    |
    v
Update GitHub Secret
    |
    v
Delete Old Key

Scoped Keys Should Be the Baseline

Before automating rotation, reduce the blast radius of the key.

NuGet scoped API keys can be restricted by:

  • Package

  • Glob pattern

  • Operation

  • Expiration

For publishing automation, the key should generally have only the permissions required by the release workflow.

For example:

Package scope:
MyCompany.*

Permission:
Push

Expiration:
30 days

This is much safer than using a broad account-wide credential.

A 30-Day Rotation Architecture

A practical design separates package publishing from credential rotation.

                    +------------------+
                    | GitHub Actions   |
                    +---------+--------+
                              |
                 +------------+------------+
                 |                         |
                 v                         v
          Package Release            Key Rotation
                 |                         |
                 v                         v
          Read GitHub Secret        Create New Key
                 |                         |
                 v                         v
          dotnet nuget push          Update Secret
                 |                         |
                 v                         v
             NuGet.org              Delete Old Key

The important principle is:

Do not wait for the publishing workflow to discover that a credential has expired.

Rotation should happen before expiration.

Store the Key in GitHub Secrets

The API key should never be committed to source control.

A release workflow can consume a repository or environment secret:

- name: Publish package
  run: >
    dotnet nuget push ./artifacts/*.nupkg
    --api-key "${{ secrets.NUGET_API_KEY }}"
    --source "https://api.nuget.org/v3/index.json"

The secret remains outside the repository.

NuGet's documentation also supports supplying an API key through the NUGET_API_KEY environment variable rather than storing it in NuGet.Config.

For CI/CD, keeping credentials in the workflow environment is generally preferable to writing them into configuration files.

Use an Environment for Production Publishing

For production packages, consider using a GitHub environment such as:

release

Then store:

NUGET_API_KEY

as an environment secret.

The workflow becomes:

jobs:
  publish:
    environment: release

    steps:
      - name: Publish package
        run: >
          dotnet nuget push ./artifacts/*.nupkg
          --api-key "${{ secrets.NUGET_API_KEY }}"
          --source "https://api.nuget.org/v3/index.json"

This separates ordinary repository automation from the production publishing credential.

The Rotation Workflow

The rotation workflow needs to perform four logical operations:

1. Create replacement key
2. Update GitHub secret
3. Verify the new credential
4. Revoke old key

The sequence matters.

Do not delete the old key first.

Instead:

Old Key
   |
   +------------------+
   |                  |
   v                  v
Create New Key     Old Key remains valid
   |
   v
Update Secret
   |
   v
Test New Key
   |
   v
Delete Old Key

This provides a recovery path if the new credential does not work.

Automating the Rotation

There is an important limitation with the traditional approach: creating and managing NuGet API keys is an account-level operation, so the rotation workflow itself needs a secure administrative authentication mechanism.

Do not solve this by placing a permanent administrator credential directly into the workflow.

Instead, use a dedicated credential with the minimum possible permissions, or migrate the publishing workflow to Trusted Publishing.

The automation concept can be represented as:

name: Rotate NuGet Credential

on:
  schedule:
    - cron: "0 3 1 * *"
  workflow_dispatch:

jobs:
  rotate:
    runs-on: ubuntu-latest

    steps:
      - name: Generate replacement credential
        run: ./scripts/create-nuget-key.sh

      - name: Update GitHub secret
        run: ./scripts/update-github-secret.sh

      - name: Validate publishing credential
        run: ./scripts/test-nuget-key.sh

      - name: Revoke previous credential
        run: ./scripts/revoke-old-key.sh

The scripts are intentionally abstract here.

The important design is the order of operations, not a hard-coded implementation of undocumented account-management APIs.

Why You Should Not Put Key Creation Logic in the Package Job

Avoid combining rotation and publishing:

Build
  |
  v
Create Key
  |
  v
Publish
  |
  v
Delete Key

This creates unnecessary coupling.

If key creation fails, the package release fails.

If package publishing fails, credential lifecycle becomes difficult to reason about.

A cleaner architecture is:

Rotation Workflow
        |
        v
Credential Lifecycle

Release Workflow
        |
        v
Package Lifecycle

They can operate independently.

Validate Before Revoking

The most important safety check is verifying the new credential before removing the old one.

A validation workflow can publish a package to a controlled test feed or perform another permitted non-production validation.

For example:

New Key
   |
   v
Authentication Test
   |
   +---- FAIL ---> Keep Old Key
   |
   +---- PASS ---> Continue
                      |
                      v
                Update Secret
                      |
                      v
                 Revoke Old

This prevents a rotation mistake from immediately breaking package releases.

Use a Rotation Metadata File

It can also be useful to track metadata without storing the secret itself.

For example:

{
  "credentialName": "nuget-release",
  "createdAt": "2026-08-01",
  "expiresAt": "2026-08-31",
  "scope": "MyCompany.*",
  "permission": "push"
}

Never store the actual API key in this file.

The purpose is operational visibility.

You can then calculate:

Days Remaining
Expiration Date
Package Scope
Rotation Status

Add an Expiration Check

Even with scheduled rotation, the release workflow should detect a missing or invalid credential.

For example:

Publish
  |
  v
Credential available?
  |
  +---- No ----> Fail clearly
  |
  +---- Yes
        |
        v
    Push package

A clear failure is better than allowing a generic authentication error to obscure the real problem.

NuGet documentation notes that an invalid or expired API key can result in a 403 response indicating that the key is invalid, expired, or lacks permission for the package.

Make Rotation Idempotent

A good rotation workflow should be safe to rerun.

Suppose the workflow runs twice.

You do not want:

Run 1 -> New Key A
Run 2 -> New Key B
Run 3 -> New Key C
...

with dozens of stale credentials.

Instead, maintain a clear ownership model:

Active Credential
       |
       v
Create Replacement
       |
       v
Validate
       |
       v
Promote Replacement
       |
       v
Revoke Previous

Keep the number of active publishing keys intentionally small.

NuGet notes that although there is no limit on the number of API keys, keeping the number manageable helps avoid stale credentials.

Use Separate Keys for Separate Packages

Suppose an organization publishes:

Company.Core
Company.Data
Company.Web
Company.AI

Avoid one unrestricted credential when separate scopes are practical.

Instead:

Company.Core -> Key A
Company.Data -> Key B
Company.Web  -> Key C
Company.AI   -> Key D

A compromise of one key then has a smaller blast radius.

NuGet's scoped-key model supports package-specific and pattern-based package scopes.

A Better GitHub Actions Release Workflow

A production package workflow can look like:

name: Publish NuGet Package

on:
  workflow_dispatch:

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release

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

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

      - name: Restore
        run: dotnet restore

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

      - name: Test
        run: dotnet test --configuration Release --no-build

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

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

This creates a clear release pipeline:

Restore
  |
  v
Build
  |
  v
Test
  |
  v
Pack
  |
  v
Publish

Credential rotation remains a separate concern.

What Happens If Rotation Fails?

Design for failure.

Scenario 1: New Key Creation Fails

Keep the old key active.

Old Key -> Still Active
New Key -> Not Created

The next rotation attempt can retry.

Scenario 2: GitHub Secret Update Fails

Do not revoke the old key.

Old Key -> Active
GitHub Secret -> Old Value
New Key -> Unused

Investigate before continuing.

Scenario 3: Validation Fails

Keep the old credential.

New Key -> Invalid
Old Key -> Active

Scenario 4: Revocation Fails

The release workflow can continue using the new key, but the old key should be flagged for cleanup.

New Key -> Active
Old Key -> Cleanup Required

This is much safer than treating every rotation step as an all-or-nothing operation.

Monitor Rotation Events

Credential rotation should produce observable events.

Record:

Rotation Started
New Credential Created
GitHub Secret Updated
Validation Passed
Old Credential Revoked

Do not log:

API Key Value

A useful notification might be:

NuGet credential rotation completed.

Package scope: Company.*
Credential: nuget-release
Status: Success
Next rotation: 30 days

This gives the team operational confidence without exposing sensitive information.

Why 30 Days Is a Useful Policy

A 30-day credential lifetime provides a relatively small exposure window.

If a key is accidentally leaked:

Day 1
  |
  v
Credential exposed
  |
  v
Maximum planned lifetime
  |
  v
Day 30
  |
  v
Credential replaced

However, the exact lifetime should depend on the organization's risk model.

Shorter credentials reduce exposure but increase operational overhead.

Longer credentials reduce rotation frequency but increase the potential lifetime of a compromised secret.

The important point is to make expiration intentional rather than accidental.

The Better Option: Trusted Publishing

There is now a more significant alternative to automated API-key rotation.

NuGet Trusted Publishing uses OIDC between the CI/CD system and nuget.org.

The workflow becomes:

GitHub Actions
      |
      v
GitHub OIDC Token
      |
      v
NuGet Trusted Publishing
      |
      v
Short-Lived API Key
      |
      v
Package Push

NuGet documents that the temporary API key issued through Trusted Publishing is valid for one hour and should be requested shortly before publishing. Each OIDC token can be exchanged only once for a temporary key.

This dramatically changes the security model.

Instead of:

Long-Lived Secret
      |
      v
Rotation

you get:

Short-Lived Identity
      |
      v
Temporary Credential
      |
      v
Publish
      |
      v
Credential Expires

There is no 30-day API key to rotate.

Trusted Publishing with GitHub Actions

NuGet's current documentation shows a GitHub Actions workflow using the id-token: write permission and the NuGet/login@v1 action to obtain a temporary API key.

A simplified example is:

jobs:
  publish:
    permissions:
      id-token: write

    steps:
      - name: NuGet login
        uses: NuGet/login@v1
        id: login
        with:
          user: ${{ secrets.NUGET_USER }}

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

The trusted publishing policy can be restricted to a specific repository, workflow file, and optionally a GitHub Actions environment.

That provides an important security boundary:

Repository
   +
Workflow
   +
Environment
   |
   v
Trusted Publisher
   |
   v
NuGet Package

API Keys vs Trusted Publishing

CapabilityRotating API KeyTrusted Publishing
Long-lived secretYesNo
Manual secret storageRequiredNot for package credential
Rotation requiredYesNo
OIDCNoYes
Temporary credentialNoYes
Package scopingYesPolicy-based
GitHub Actions supportYesYes
Operational complexityHigherLower after setup
Recommended for new CI/CDAcceptablePreferred

NuGet describes Trusted Publishing as a way to publish packages without managing long-lived API keys.

Migration Strategy

If your organization currently uses API keys, do not change everything during a release.

A safer migration looks like:

Existing API Key
      |
      v
Implement Trusted Publishing
      |
      v
Test in Non-Production
      |
      v
Publish Test Package
      |
      v
Validate
      |
      v
Production Release
      |
      v
Remove Old API Key

This provides a controlled transition.

Common Mistakes

Rotating After Expiration

If you rotate only after a release fails, the rotation process itself becomes an outage dependency.

Using an Account-Wide Key

Broad credentials increase the blast radius of compromise.

Deleting the Old Key Too Early

Always validate the replacement before revocation.

Logging Secrets

Never print API keys in workflow logs.

Combining Rotation and Release

Keep credential lifecycle separate from package lifecycle.

Creating Too Many Keys

Unused credentials become security debt.

Ignoring Trusted Publishing

For GitHub Actions-based publishing, OIDC-based Trusted Publishing can remove much of the credential-management burden entirely.

Best Practices

Use this checklist when designing a NuGet publishing system:

[ ] Use scoped API keys
[ ] Restrict package permissions
[ ] Store secrets in GitHub Secrets
[ ] Use production environments
[ ] Rotate before expiration
[ ] Validate replacement credentials
[ ] Revoke old credentials
[ ] Never log key values
[ ] Monitor rotation events
[ ] Keep credential count low
[ ] Separate rotation from release
[ ] Test failed-rotation scenarios
[ ] Prefer OIDC Trusted Publishing where available

Advantages and Disadvantages

Advantages

  • Reduces the lifetime of publishing credentials.

  • Limits the impact of leaked keys.

  • Makes credential lifecycle explicit.

  • Supports automated CI/CD operations.

  • Encourages least-privilege package publishing.

Disadvantages

  • Traditional key rotation requires account-management automation.

  • Rotation workflows themselves need secure administrative credentials.

  • Poorly designed rotation can interrupt package publishing.

  • Multiple packages can make key management complicated.

  • Migration to Trusted Publishing requires initial configuration and testing.

Conclusion

Automating NuGet API key rotation is a useful security improvement for existing GitHub Actions pipelines, especially when scoped credentials are used and the rotation process validates a replacement before revoking the previous key.

But the bigger architectural lesson is that credential rotation is often a symptom of long-lived authentication.

NuGet Trusted Publishing provides a stronger model for GitHub Actions by using OIDC to obtain short-lived credentials for individual publishing workflows. The temporary NuGet credential is valid for only one hour, eliminating the need to maintain a 30-day publishing secret.

For an existing system that cannot immediately migrate, a 30-day rotation policy is a practical improvement. For new .NET package publishing pipelines, however, the better target is a keyless or short-lived identity model where GitHub Actions proves who is publishing rather than continuously carrying a reusable publishing secret.