This error occurs when you've exceeded GitHub's API rate limits, typically during unauthenticated requests. The fix is to authenticate with GitHub using a personal access token or SSH key, which significantly increases your rate limit allowance.
Fixes 403
curl -s https://api.github.com/rate_limit | grep -A 4 '"core"'curl -s https://api.github.com/rate_limit | grep -A 4 '"core"'fatal: unable to access: The requested URL returned error: 403 rate limit exceeded
GitHub imposes rate limits on API requests to prevent abuse and ensure service availability for all users. When you encounter a "403 rate limit exceeded" error, it means you've made too many requests within a specific time window. The rate limits differ dramatically based on authentication: - **Unauthenticated requests**: 60 requests per hour (shared across your IP address) - **Authenticated requests**: 5,000 requests per hour (tied to your user account) This error commonly occurs when: - Cloning repositories over HTTPS without authentication - Running scripts that make many API calls - Using CI/CD pipelines without proper credentials - Multiple developers sharing the same IP (office network, VPN) - Automated tools or bots hitting the API frequently
First, verify your current rate limit status to understand the scope of the issue:
curl -s https://api.github.com/rate_limit | grep -A 4 '"core"'For authenticated requests (replace TOKEN with your token):
curl -s -H "Authorization: token YOUR_TOKEN" https://api.github.com/rate_limitLook at the remaining value - if it's 0, you've hit the limit. The reset timestamp shows when your limit will refresh.
Generate a personal access token to authenticate your Git operations:
1. Go to GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)
2. Or use this direct link: https://github.com/settings/tokens/new
3. Click "Generate new token (classic)"
4. Give it a descriptive name (e.g., "Git CLI access")
5. Select the required scopes:
- repo - Full control of private repositories
- read:org - If you need to access organization repos
6. Click "Generate token" and copy it immediately (you won't see it again)
For fine-grained tokens (newer, more secure):
1. Go to Developer settings > Personal access tokens > Fine-grained tokens
2. Set repository access and permissions as needed
Store your credentials so Git uses them automatically:
Option A: Using Git credential helper (recommended)
# Enable credential caching (stores in memory for 1 hour)
git config --global credential.helper cache
# Or store credentials permanently (less secure but convenient)
git config --global credential.helper storeThen run any Git command that requires auth - you'll be prompted for username and password. Use your GitHub username and the PAT as password.
Option B: Include token in remote URL (not recommended for shared machines)
git remote set-url origin https://[email protected]/username/repo.gitOption C: Using GitHub CLI (gh)
# Install gh CLI and authenticate
gh auth loginThis automatically configures Git credentials.
SSH keys provide authenticated access without tokens and aren't subject to the same rate limits:
Generate an SSH key (if you don't have one):
ssh-keygen -t ed25519 -C "[email protected]"Add the public key to GitHub:
# Copy your public key
cat ~/.ssh/id_ed25519.pubThen add it at: https://github.com/settings/ssh/new
Update your remote URL to use SSH:
git remote set-url origin [email protected]:username/repo.gitTest the connection:
ssh -T [email protected]You should see: "Hi username! You've successfully authenticated..."
If you need immediate access and can't authenticate right now, you can wait for the limit to reset:
Check when your limit resets:
curl -s -I https://api.github.com/users/octocat | grep -i x-ratelimitThe X-RateLimit-Reset header contains a Unix timestamp. Convert it to a readable time:
# Linux
date -d @1234567890
# macOS
date -r 1234567890Rate limits typically reset hourly. For unauthenticated requests, the 60-request limit resets every hour.
For CI/CD pipelines, use repository secrets to store your token:
GitHub Actions:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }} # Built-in token for same repo
# Or use a PAT for cross-repo access:
# token: ${{ secrets.PAT_TOKEN }}GitLab CI:
variables:
GIT_CREDENTIALS: "https://oauth2:${GITHUB_TOKEN}@github.com"Jenkins:
Store the token in Jenkins credentials and inject it:
withCredentials([string(credentialsId: 'github-token', variable: 'TOKEN')]) {
sh 'git clone https://${TOKEN}@github.com/user/repo.git'
}### Understanding GitHub Rate Limits
GitHub has several types of rate limits:
| Type | Unauthenticated | Authenticated |
|------|-----------------|---------------|
| Core API | 60/hour | 5,000/hour |
| Search API | 10/minute | 30/minute |
| GraphQL API | N/A | 5,000 points/hour |
| Git operations | Varies | Higher limits |
### GitHub Enterprise Considerations
If you're using GitHub Enterprise Server, rate limits are configured by your administrator and may differ from github.com. Check with your admin or consult your instance's documentation.
### Conditional Requests
Use conditional requests to avoid counting against your rate limit when data hasn't changed:
# Save the ETag from a previous request
curl -H "If-None-Match: \"abc123\"" https://api.github.com/repos/owner/repo
# Returns 304 Not Modified if unchanged (doesn't count against limit)### Token Scopes and Rate Limits
Different token scopes don't affect rate limits - all authenticated requests get 5,000/hour. However, using a token with minimal required scopes is a security best practice.
### Secondary Rate Limits
GitHub also has secondary rate limits based on:
- Concurrent requests (avoid parallel bulk operations)
- Total requests per minute (even if under hourly limit)
- CPU time for expensive operations
If you hit secondary limits, you'll see a different error message asking you to slow down.
### Monitoring Rate Limit Usage
For applications making many API calls, monitor your usage:
curl -s -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/rate_limit | jq '.resources.core'Implement exponential backoff when limits are low:
import time
remaining = int(response.headers['X-RateLimit-Remaining'])
if remaining < 10:
reset_time = int(response.headers['X-RateLimit-Reset'])
sleep_time = reset_time - time.time() + 1
time.sleep(max(0, sleep_time))