| | |

GitLab 03 🦊 Managing SSH Keys and Personal Access Tokens (PATs)

⏱️ Estimated read: 11 min | |

GitLab supports two primary methods for authenticating Git operations and API requests: SSH keys and Personal Access Tokens (PATs). SSH keys are the preferred method for Git operations because they provide a secure, passwordless workflow after the initial setup. PATs are the mechanism for authenticating against the GitLab API, and they serve as the password substitute when cloning over HTTPS, since GitLab no longer supports account passwords for Git over HTTPS . Understanding the distinction is critical: SSH keys authenticate the machine to the Git server, while PATs authenticate an application or script to the GitLab API and the HTTPS Git endpoint.

Key point: One SSH key authenticates you to every project and group your account can access. You do not need a separate key for each project . PATs are scoped tokens with expiration dates. In GitLab 16.0 and later, new PATs must have an expiry date, defaulting to 365 days if not specified .


Why SSH keys and PATs exist

The password elimination problem. GitLab removed support for account passwords over HTTPS for Git operations. A developer who attempts to git clone over HTTPS with their account password will fail. The two replacements are SSH keys and PATs. SSH keys are the recommended path for interactive development because they are configured once and then work silently . PATs are the fallback when SSH is blocked by a firewall or when the workflow requires HTTPS .

The key management problem. An SSH key pair consists of a public key and a private key. The public key is uploaded to GitLab. The private key stays on the local machine and is never transmitted. The public key cannot be used to derive the private key, so uploading it is safe . The private key must be protected; if it is compromised, an attacker can authenticate as the user.

The token scoping problem. A PAT is not a master key. It is created with specific scopes (api, read_user, read_repository, write_repository, etc.). A token scoped only to read_repository cannot push code. This limits the damage if the token is leaked. The fine-grained PAT model, generally available in GitLab 19.2, goes further: the token is scoped to specific resources and permissions, and the token’s permissions are intersected with the user’s own permissions at request time .

The expiration problem. Tokens that never expire are a security liability. GitLab 16.0 made expiration mandatory for new PATs. The default is 365 days. GitLab sends email notifications at 7 days, 30 days, and 60 days before expiration . The expiration calendar (/-/user_settings/personal_access_tokens.ics) can be subscribed to for advance warning.

The trade-off. SSH keys are more convenient for daily Git operations but require key management (generation, rotation, revocation). PATs are more flexible for automation and API access but require scope selection and expiration management. A developer typically uses both: SSH for Git, PATs for API scripts and CI pipelines.


a. Generating and adding an SSH key

The first step is checking for an existing key pair. The .ssh/ directory in the home folder may already contain id_ed25519 or id_rsa. If a key pair exists, it can be reused . If not, a new pair is generated with ssh-keygen.

# Generate an ED25519 key (preferred)
ssh-keygen -t ed25519 -C "your_email@example.com"

# For systems that require RSA
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

The ED25519 algorithm is preferred because it is more secure and performant than RSA. RSA keys, if used, should be at least 4096 bits . The -C flag adds a comment (typically the email address) to the public key, which helps identify it later.

The public key is copied to GitLab. The private key stays on the local machine. The public key is in ~/.ssh/id_ed25519.pub.

# Display the public key
cat ~/.ssh/id_ed25519.pub

The public key is added to GitLab through the user settings: Edit profile → SSH Keys → Add new key. The key is pasted into the form, and an expiration date can be set. After adding, the connection is verified with ssh -T git@gitlab.com .

# Verify the connection
ssh -T git@gitlab.com
# Expected output: Welcome to GitLab, @username!

For self-managed GitLab, the hostname is the instance’s domain (e.g., git@gitlab.example.com) rather than gitlab.com .


b. Creating and using a Personal Access Token

A PAT is created from the user settings: Edit profile → Access → Personal access tokens. The form requires a name, an expiration date, and at least one scope. The token value is displayed once and cannot be retrieved after the page is refreshed .

The scopes determine what the token can do. The api scope grants full access to the API. The read_repository and write_repository scopes are sufficient for Git operations over HTTPS. The read_user scope allows reading user information. For automation that only needs to read code, read_repository is the minimum .

A PAT can be used to clone a repository over HTTPS when SSH is not available. The token is used as the password.

# Clone using PAT (saves token in bash history)
git clone https://<username>:<personal_token>@gitlab.com/group/project.git

# Safer: avoid saving token in history
git clone https://<username>@gitlab.com/group/project.git
# When prompted for password, enter the personal token

The username in the clone command can be any non-empty string. It does not have to match the actual username . For CI/CD pipelines, the token is passed as an environment variable or stored in a CI/CD variable, never hardcoded in a script.

The PAT is also used for API requests. The token is passed in the PRIVATE-TOKEN header.

# API request with PAT
curl --header "PRIVATE-TOKEN: <your_access_token>" \
  "https://gitlab.com/api/v4/projects"

c. Rotation, revocation, and fine-grained tokens

A PAT can be rotated, which generates a new token value while preserving the token’s name, scopes, and expiration date. Tools that rely on the old token stop working until they are updated with the new value. Rotation is the correct response to a suspected leak that does not require revoking the token’s permissions entirely .

Revocation immediately invalidates the token. GitLab retains the token for audit purposes; it is not permanently deleted. A revoked token cannot be used, and the tools that relied on it will fail until a new token is created .

The fine-grained PAT model, generally available in GitLab 19.2, replaces the coarse scopes (api, read_repository) with resource-specific permissions. A token can be scoped to a specific project and granted only read_repository on that project’s repository. The token’s permissions are checked against the user’s permissions on every request: the token can only perform an action if both the token and the user are authorized . This is the strongest form of PAT scoping and the recommended model for automation that accesses a limited set of resources.


Complete Example Session

# ============================================
# PART 1: CHECK FOR EXISTING SSH KEYS
# ============================================

ls ~/.ssh/
# If id_ed25519 and id_ed25519.pub exist, skip to PART 3.
# ============================================
# PART 2: GENERATE A NEW SSH KEY
# ============================================

ssh-keygen -t ed25519 -C "your_email@example.com"
# Press Enter for default path and no passphrase.
# ============================================
# PART 3: ADD THE PUBLIC KEY TO GITLAB
# ============================================

cat ~/.ssh/id_ed25519.pub
# Copy the output.
# GitLab → Edit profile → SSH Keys → Add new key
# Paste the key and save.
# ============================================
# PART 4: VERIFY THE SSH CONNECTION
# ============================================

ssh -T git@gitlab.com
# Expected: Welcome to GitLab, @username!
# ============================================
# PART 5: CLONE USING SSH
# ============================================

git clone git@gitlab.com:group/project.git
# No password prompt.
# ============================================
# PART 6: CREATE A PERSONAL ACCESS TOKEN
# ============================================

# GitLab → Edit profile → Access → Personal access tokens
# Name: ci-read-token
# Expiration: 30 days
# Scopes: read_repository
# Generate token
# Copy the token value immediately.
# ============================================
# PART 7: CLONE USING PAT (HTTPS)
# ============================================

git clone https://username@gitlab.com/group/project.git
# When prompted for password, enter the PAT.
# ============================================
# PART 8: USE PAT FOR API
# ============================================

curl --header "PRIVATE-TOKEN: <token>" \
  "https://gitlab.com/api/v4/projects?membership=true"
# ============================================
# PART 9: ROTATE OR REVOKE
# ============================================

# Rotate: token page → vertical ellipsis → Rotate
# Revoke: token page → vertical ellipsis → Revoke

The nine parts trace the authentication lifecycle: key check, key generation, public key upload, SSH verification, SSH clone, PAT creation, HTTPS clone with PAT, API request with PAT, and rotation/revocation.


Quick Reference

SSH Key Types

AlgorithmPreferredNotes
ED25519YesMore secure and performant
RSANoUse at least 4096 bits
ECDSANoSecurity concerns

PAT Scopes

ScopePurpose
apiFull API access
read_userRead user information
read_repositoryClone/pull over HTTPS
write_repositoryPush over HTTPS

PAT Lifecycle

ActionEffect
RotateNew token value, same scopes/expiry
RevokeImmediate invalidation
ExpireAutomatic at midnight UTC on expiry date

SSH vs PAT

AspectSSH KeyPAT
Git operationsYesYes (HTTPS)
API requestsNoYes
PasswordlessYes (after setup)No (token as password)
ExpirationOptionalMandatory since 16.0

Best Practices

✅ Do This:

ssh-keygen -t ed25519 -C "email@example.com"
<!-- Use ED25519 for new keys -->                     # ✅
ssh -T git@gitlab.com
<!-- Verify SSH connection after adding key -->       # ✅
# Set expiration on PATs
<!-- Default is 365 days if not specified -->         # ✅

❌ Don’t Do This:

# Share the private key
<!-- Private key must never leave the local machine --> # ❌
# Create a PAT with the "api" scope for CI read-only
<!-- Use read_repository instead -->                 # ❌
# Hardcode PAT in a script
<!-- Use CI/CD variables or environment variables --> # ❌

Common Pitfalls

PitfallWhy It HappensFix
SSH connection refusedKey not added to GitLabAdd public key in SSH Keys settings
Permission denied (publickey)Wrong key or not loadedssh-add ~/.ssh/id_ed25519
HTTPS clone failsUsing account passwordUse PAT as password
PAT not workingExpired or wrong scopeCheck expiration and scopes
Token leaked in historyCloned with token in URLUse the prompt-based method

Real-World Examples

1. Generate ED25519 Key

ssh-keygen -t ed25519 -C "email@example.com"

2. Generate RSA Key

ssh-keygen -t rsa -b 4096 -C "email@example.com"

3. Display Public Key

cat ~/.ssh/id_ed25519.pub

4. Test SSH

ssh -T git@gitlab.com

5. Clone via SSH

git clone git@gitlab.com:group/project.git

6. Clone via HTTPS with PAT

git clone https://username@gitlab.com/group/project.git

7. API with PAT

curl --header "PRIVATE-TOKEN: <token>" https://gitlab.com/api/v4/user

8. Rotate PAT

Token page → vertical ellipsis → Rotate

9. Revoke PAT

Token page → vertical ellipsis → Revoke

10. Fine-grained PAT

Generate token → Fine-grained token → select resources and permissions

Visual

SSH Authentication Flow

┌──────────────────────────────────────────────┐
│  Local machine                               │
│  ~/.ssh/id_ed25519 (private)                 │
│  ~/.ssh/id_ed25519.pub (public)              │
└──────────────────┬───────────────────────────┘
                   │
                   │ public key uploaded
                   ▼
┌──────────────────────────────────────────────┐
│  GitLab (SSH Keys settings)                  │
│  Stores public key                           │
└──────────────────┬───────────────────────────┘
                   │
                   │ git push/pull
                   ▼
┌──────────────────────────────────────────────┐
│  SSH handshake                               │
│  GitLab verifies signature with public key   │
│  Access granted without password             │
└──────────────────────────────────────────────┘

PAT Authentication Flow

┌──────────────────────────────────────────────┐
│  PAT created with scopes and expiry          │
│  Value displayed once                        │
└──────────────────┬───────────────────────────┘
                   │
                   │ used as password or header
                   ▼
┌──────────────────────────────────────────────┐
│  Git over HTTPS                              │
│  or API request                              │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  GitLab validates token                      │
│  Checks scopes, expiry, user permissions     │
│  Access granted or denied                    │
└──────────────────────────────────────────────┘

Summary

ItemValue
Preferred SSH keyED25519
RSA key size≥ 4096 bits
SSH verificationssh -T git@gitlab.com
PAT locationEdit profile → Access → Personal access tokens
PAT expiry default365 days
PAT clone passwordToken value
Fine-grained PATGenerally available in 19.2
RotationPreserves scopes, new value

Key takeaways:

  • SSH keys are the preferred method for Git operations. One key pair authenticates to every project and group the user can access. ED25519 is the recommended algorithm .
  • PATs replace account passwords for HTTPS. GitLab no longer supports passwords over HTTPS. A PAT is used as the password in the clone prompt or in the PRIVATE-TOKEN header for API calls .
  • PATs have mandatory expiration. Since GitLab 16.0, new PATs must have an expiry date, defaulting to 365 days. Notifications are sent before expiration .
  • Fine-grained PATs provide resource-level scoping. Available in GitLab 19.2, they allow a token to be limited to specific projects and specific permissions, and the token’s permissions are intersected with the user’s permissions .
  • The private key is never transmitted. Only the public key is uploaded to GitLab. The private key stays on the local machine and must be protected .

Remember: SSH keys authenticate the machine for Git operations. PATs authenticate scripts and API clients. The SSH key is configured once and works silently. The PAT is created with scopes and an expiry date and must be rotated or revoked when compromised. The public key is safe to share. The private key and the PAT value are secrets.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!