NuGet API Key Management: Best Practices for Teams
How to generate, scope, rotate, and secure your NuGet API keys without slowing down your team.
Every NuGet package push requires an API key. For a solo developer pushing to nuget.org, a single key is simple enough. But when a team is involved, with multiple packages, private feeds, and CI/CD pipelines, API key management becomes a real security concern.
This guide covers how NuGet API keys work, how to scope and rotate them properly, and how to avoid the mistakes that lead to leaked credentials and unauthorized publishes.
1. What is a NuGet API key
A NuGet API key is a secret token that authenticates you when pushing packages to a NuGet feed. When you run dotnet nuget push, the CLI sends your API key in the X-NuGet-ApiKey HTTP header. The server checks this key to verify you have permission to publish.
On nuget.org, API keys are tied to your Microsoft account. On private feeds (Azure Artifacts, GitHub Packages, pkgstore, or self-hosted servers), they work differently depending on the platform. But the principle is the same: whoever holds the key can push packages.
That is why managing these keys matters. A leaked API key means someone can publish malicious versions of your packages. A shared key with no scope means any team member can overwrite any package. Poor key hygiene is a supply chain attack waiting to happen.
2. Generating API keys on nuget.org
To create an API key on nuget.org:
- Sign in at nuget.org with your Microsoft account.
- Go to your account settings and select API Keys.
- Click Create.
- Give the key a descriptive name (e.g. "GitHub Actions - MyLibrary").
- Set an expiration date. The maximum on nuget.org is 365 days.
- Choose the scope: push new packages, push updates only, or unlist.
- Select which packages the key can access (by package name or glob pattern).
- Copy the key immediately. nuget.org will not show it again.
Tip: Always copy your API key to a password manager or secret store right after generating it. If you lose it, you will need to regenerate a new one and update every system that uses it.
One common mistake is creating a single "admin" key with push access to all packages, then sharing it across the team. This works until someone accidentally pushes to the wrong package or the key leaks in a build log. Instead, create one key per purpose.
3. Scoping keys to specific packages
The most important security practice for NuGet API keys is scoping. A scoped key can only push to specific packages, which limits the blast radius if the key is compromised.
On nuget.org
nuget.org supports two levels of scoping:
- Package-level scope: The key can only push to packages you explicitly list. Use this when each key is tied to a specific project or library.
- Glob pattern scope: The key can push to any package matching a pattern, like MyCompany.*. This is useful when a CI pipeline publishes multiple packages under a common prefix.
You can also restrict what operations a key can perform:
- Push new packages and package versions: full publish access.
- Push new package versions only: can update existing packages but not register new ones.
- Unlist packages: can hide packages from search results but not delete them.
The ideal setup for a team: each CI pipeline gets its own key, scoped to the exact packages it builds, with "push new package versions only" permission. No one person holds a key that can do everything.
On private feeds
Scoping works differently depending on the platform. Azure Artifacts uses Azure DevOps permissions (contributor, reader, etc.) rather than per-key scoping. GitHub Packages ties permissions to repository access.
On pkgstore, API keys (called push keys) can be scoped to a specific feed or to all feeds under your publisher account. Feed-scoped keys are the default, so a key for your "Pro" feed cannot accidentally push to your "Enterprise" feed.
4. API keys for private feeds
Private NuGet feeds add another layer: not only do publishers need push keys, but consumers need credentials to restore packages. These are two different concerns.
Push keys (publishers)
Push keys authenticate the dotnet nuget push command. They should be treated like deployment credentials: stored in a secret manager, scoped tightly, and rotated regularly.
dotnet nuget push MyLib.1.0.0.nupkg \
--source https://www.pkgstore.io/api/nuget/mycompany/mylib \
--api-key YOUR_PUSH_KEY
Subscriber credentials (consumers)
Consuming packages from a private feed requires authentication too. This is typically a username/password pair or a personal access token, not the same API key used for pushing.
dotnet nuget add source https://www.pkgstore.io/api/nuget/mycompany/mylib/index.json \
--name MyLib \
--username [email protected] \
--password YOUR_CREDENTIAL \
--store-password-in-clear-text
Important: The --store-password-in-clear-text flag stores your credentials in plain text in the NuGet config file. On CI servers this is usually acceptable because the config is ephemeral. On developer machines, consider using a credential provider or environment variables instead.
The key distinction: push keys and subscriber credentials should never be the same. If a subscriber's credential is compromised, they should not be able to push malicious packages to your feed.
5. Using API keys in CI/CD pipelines
Most package publishes should happen from CI, not from a developer's machine. This removes the "it works on my machine" problem and creates an auditable trail of who published what.
GitHub Actions
Store your NuGet API key as a repository secret, then reference it in your workflow:
- name: Push to NuGet
run: dotnet nuget push **/*.nupkg
--source https://api.nuget.org/v3/index.json
--api-key ${{ secrets.NUGET_API_KEY }}
--skip-duplicate
Azure DevOps Pipelines
Use a service connection or a pipeline variable marked as secret:
- task: NuGetCommand@2
inputs:
command: push
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
nuGetFeedType: external
publishFeedCredentials: 'NuGet-ServiceConnection'
GitLab CI
publish:
script:
- dotnet nuget push **/*.nupkg
--source https://api.nuget.org/v3/index.json
--api-key $NUGET_API_KEY
--skip-duplicate
only:
- tags
Private feeds in CI
For pushing to a private feed from CI, the setup is the same but the source URL changes:
- name: Push to private feed
run: dotnet nuget push **/*.nupkg
--source https://www.pkgstore.io/api/nuget/mycompany/mylib
--api-key ${{ secrets.PKGSTORE_PUSH_KEY }}
--skip-duplicate
Tip: Use the --skip-duplicate flag to avoid failures when a package version already exists. This is especially useful for builds triggered by tags or release branches where the same version might be pushed more than once.
Three rules for CI/CD API key management:
- Never hardcode keys in pipeline files. Use secrets or variable groups.
- Scope the key to exactly what the pipeline publishes. A pipeline that builds MyCompany.Utils should not hold a key that can push MyCompany.Core.
- Mask keys in build logs. Most CI platforms do this automatically for secrets, but verify that your key does not appear in diagnostic or verbose output.
6. Key rotation strategies
API keys should not live forever. Regular rotation limits the window of exposure if a key is compromised. Here is a practical rotation strategy that balances security with convenience.
How often to rotate
- nuget.org keys: Every 90 to 180 days. nuget.org enforces a maximum 365-day expiration, so you will be forced to rotate at least annually.
- Private feed push keys: Every 90 days for active feeds, or immediately when a team member with access leaves.
- CI/CD pipeline keys: Every time you rotate the underlying API key. Update the secret in your CI platform and trigger a test build to verify.
Rotation without downtime
The trick to seamless rotation is overlapping validity periods:
- Generate a new key while the old one is still valid.
- Update all systems that use the old key (CI pipelines, developer machines).
- Verify the new key works by pushing a test package or running a pipeline.
- Revoke the old key only after confirming the new one is active everywhere.
If your platform supports it, tag keys with metadata (creation date, intended pipeline, owner) so you can audit which keys are still in use and which are stale.
Emergency rotation
If you suspect a key has been compromised (committed to a public repo, visible in logs, or a team member leaves on bad terms):
- Revoke the key immediately. Do not wait to set up a replacement first. Stopping unauthorized access is the priority.
- Audit recent publishes. Check if any unexpected package versions were pushed while the key was exposed.
- Generate a new key and update all systems.
- If malicious packages were pushed, unlist or delete them and notify your consumers.
7. Common mistakes and how to fix them
- Committing keys to source control. This is the most common leak vector. Even if you delete the commit, the key remains in git history. Use .gitignore for any file that contains keys, and add a pre-commit hook or use a tool like gitleaks to scan for secrets.
- Sharing one key across the entire team. When everyone uses the same key, you cannot audit who pushed what, and revoking the key disrupts everyone. Give each developer or pipeline its own key.
- Using unscoped keys. A key with access to all packages is a single point of failure. If it leaks, every package you maintain is at risk. Always scope keys to the minimum set of packages needed.
- Storing keys in NuGet.config checked into the repo. The nuget.config file is meant for source URLs and feed configuration, not credentials. If you must store credentials locally, use the user-level config at %APPDATA%\NuGet\NuGet.Config (Windows) or ~/.nuget/NuGet/NuGet.Config (Linux/macOS).
- Never rotating keys. A key created two years ago and never changed is a liability. Set a calendar reminder or use your CI platform's secret expiration feature.
- Using push keys as restore credentials. Push keys should only be used for publishing. Consumers restoring packages should use separate, read-only credentials. Mixing the two means a compromised consumer can push malicious packages.
8. Setting up a team key policy
For teams managing more than a handful of packages, informal key management does not scale. Here is a simple policy template you can adapt:
NuGet API Key Policy
- Key ownership: Every key is owned by either a person or a CI pipeline. No shared keys.
- Naming convention: Keys are named with the format [owner]-[target]-[date], e.g. gh-actions-mylib-2026-04.
- Scope: Keys are scoped to the specific packages or feeds they need. No wildcard keys unless a pipeline publishes 10+ packages with a shared prefix.
- Storage: Keys are stored in the team's secret manager (e.g. Azure Key Vault, GitHub Secrets, 1Password). Never in source control, wikis, or chat messages.
- Rotation: Keys are rotated every 90 days. The person or team responsible for the pipeline owns the rotation.
- Offboarding: When a team member leaves, all keys they owned are revoked within 24 hours.
- Incident response: If a key is suspected compromised, revoke first, investigate second.
This does not need to be a formal document. A section in your team's README or wiki is enough. The point is that everyone knows the rules before they create a key.
Need scoped push keys for your private feed?
pkgstore gives you feed-scoped push keys out of the box. Each key is tied to a specific feed, with separate subscriber credentials for consumers. No manual access control, no shared secrets.
Start publishing