How to Configure SSH Keys for Checkout in GitHub Actions: A Complete Guide

To configure SSH keys for repository checkout in GitHub Actions, provide your private key via the ssh-key input in actions/checkout, which automatically writes the credential to a temporary file, configures Git's core.sshCommand, and securely removes all sensitive data after job completion.

The actions/checkout action provides native support for SSH authentication, enabling secure access to private repositories and submodules without manual credential management on runners. When you configure SSH keys for checkout in GitHub Actions using the dedicated inputs, the action handles temporary file creation, host verification, and automatic cleanup according to GitHub's security model.

SSH Authentication Inputs

The action exposes four inputs specifically for SSH configuration, defined in action.yml and consumed by src/input-helper.ts.

The Four Key Inputs

  • ssh-key: The private SSH key content, typically stored as an encrypted GitHub Secret. This key is written to a temporary file in $RUNNER_TEMP during execution.
  • ssh-known-hosts: Additional SSH host keys to trust, formatted as you would see in a standard known_hosts file. Use ssh-keyscan to generate entries for internal Git servers.
  • ssh-strict: Boolean flag defaulting to true. When enabled, the SSH command includes StrictHostKeyChecking=yes and CheckHostIP=no to prevent man-in-the-middle attacks.
  • ssh-user: The username for SSH connections, defaulting to git for standard GitHub repositories.

How the Action Processes SSH Credentials

The implementation spans two critical source files that handle input parsing and authentication configuration.

Input Parsing in input-helper.ts

In src/input-helper.ts, the getInputs() function reads workflow inputs and populates the IGitSourceSettings interface:

// Conceptual representation based on source implementation
result.sshKey = core.getInput('ssh-key')
result.sshKnownHosts = core.getInput('ssh-known-hosts')
result.sshStrict = core.getBooleanInput('ssh-strict')
result.sshUser = core.getInput('ssh-user') || 'git'

SSH Configuration in git-auth-helper.ts

The src/git-auth-helper.ts file contains the configureAuth() method, which calls configureSsh() to establish the authentication context. This process performs four critical operations:

  1. Writes the private key from ssh-key to a temporary file with restricted permissions (0600)
  2. Writes known hosts data to $RUNNER_TEMP if provided via ssh-known-hosts
  3. Constructs an SSH command string combining the key path, user, and strictness options
  4. Executes git config core.sshCommand to persist the settings for all subsequent Git operations

When ssh-strict is true (default), the generated command resembles:

ssh -i "$RUNNER_TEMP/ssh-key-random-id" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/known_hosts" -l git

When ssh-strict is false, the StrictHostKeyChecking and CheckHostIP options are omitted, allowing connections to hosts not present in known_hosts.

Temporary File Handling and Cleanup

The action generates unique temporary filenames for SSH keys to prevent collisions between concurrent jobs. After job completion, a post-step defined in the action metadata removes:

  • The temporary private key file from $RUNNER_TEMP
  • The temporary known-hosts file (if created)
  • The core.sshCommand Git configuration entry

This ensures no SSH credentials persist on self-hosted or GitHub-hosted runners after the workflow finishes.

Step-by-Step Workflow Configuration

Basic SSH Checkout Example

Store your SSH private key as a repository secret (typically named SSH_PRIVATE_KEY), then reference it in your workflow:

name: Checkout with SSH
on: [push]

jobs:
  checkout:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout private repository
        uses: actions/checkout@v4
        with:
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}

This minimal configuration uses defaults: strict host checking enabled, git as the user, and GitHub's pre-installed host keys.

Advanced Configuration with Known Hosts

For internal Git servers or enhanced security pinning, provide explicit host keys:

      - name: Checkout with custom hosts
        uses: actions/checkout@v4
        with:
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-known-hosts: |
            github.com ssh-rsa AAAAB3NzaC1yc2EAAA...
            git.company.internal ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
          ssh-user: git

Generate host keys using ssh-keyscan git.company.internal locally before adding them to your secret or workflow file.

Disabling Strict Host Checking

For testing environments or dynamically provisioned hosts where host keys change frequently, disable strict checking:

      - name: Checkout without strict verification
        uses: actions/checkout@v4
        with:
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-strict: false

Security Warning: Setting ssh-strict: false removes protection against man-in-the-middle attacks. Only use this in isolated, trusted network environments.

Security and Implementation Details

Why core.sshCommand Instead of GIT_SSH_COMMAND

The action configures Git's core.sshCommand rather than the GIT_SSH_COMMAND environment variable. According to the actions/checkout source code, this approach ensures:

  • Persistence across subprocesses: Submodule fetches and subsequent git commands inherit the SSH configuration automatically
  • Isolation: Settings are scoped to the repository context rather than the entire shell environment
  • Cleanup: Git configuration entries are easier to reset than environment variables in action post-processing

This method ensures that nested Git operations (like recursive submodule updates) authenticate correctly without exposing credentials to parent processes.

Summary

  • The actions/checkout action accepts four SSH-related inputs (ssh-key, ssh-known-hosts, ssh-strict, and ssh-user) processed in src/input-helper.ts and applied via src/git-auth-helper.ts
  • SSH keys are written to temporary files in $RUNNER_TEMP with 0600 permissions, and core.sshCommand is configured to use these credentials for all Git operations
  • Strict host checking (ssh-strict: true) adds StrictHostKeyChecking=yes and CheckHostIP=no to prevent connection to untrusted hosts, while false allows connections to any host
  • Automatic post-job cleanup removes temporary key files and Git configuration entries, ensuring no credentials leak between jobs or persist on runners
  • Use ssh-known-hosts to pin specific host keys for internal repositories, or set ssh-strict: false for testing environments (with security trade-offs)

Frequently Asked Questions

Where should I store the SSH private key in GitHub Actions?

Store the SSH private key as an encrypted repository secret or organization secret, then reference it via ${{ secrets.SECRET_NAME }} in the ssh-key input. Never hardcode SSH keys directly in workflow files. The action writes this key to a temporary file during execution and deletes it immediately after the job completes according to the cleanup logic in git-auth-helper.ts.

What is the difference between ssh-strict true and false?

When ssh-strict is true (default), the action includes StrictHostKeyChecking=yes and CheckHostIP=no in the SSH command, requiring the host to exist in known_hosts and preventing DNS spoofing attacks. When false, these options are omitted from the core.sshCommand configuration, allowing connections to hosts whose keys haven't been cached, which carries security risks but enables connections to new or dynamically provisioned hosts.

How does actions/checkout clean up SSH keys after the job?

The action registers a post-step that executes after your job finishes, calling cleanup functions in git-auth-helper.ts that delete the temporary private key file and known-hosts file from $RUNNER_TEMP, then unset the repository's core.sshCommand configuration. This ensures ephemeral credentials never persist on the runner for subsequent jobs or workflow runs.

Can I use SSH authentication for submodules?

Yes. When you configure SSH keys using the ssh-key input, the core.sshCommand configuration persists for all Git operations in the repository, including recursive submodule fetches. If your submodules reside in different repositories requiring different SSH keys, you must configure authentication separately after the initial checkout or use deploy keys with access to multiple repositories.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →