# How actions/checkout Handles SSH Authentication with the ssh-key Input

> Discover how actions/checkout uses the ssh-key input to securely authenticate SSH connections by creating a temporary key file and setting a custom GIT_SSH_COMMAND for Git operations.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: how-to-guide
- Published: 2026-08-29

---

**The `actions/checkout` action implements SSH authentication by collecting the `ssh-key` input, securely writing it to a temporary file with restricted permissions, constructing a custom `GIT_SSH_COMMAND` with host verification settings, and injecting it into the Git environment.**

When you configure the `actions/checkout` action with the `ssh-key` input, it initiates a multi-stage authentication pipeline written in TypeScript. This process securely manages private keys, handles host verification through `known_hosts`, and ensures credentials are cleaned up after the job completes.

## Parsing SSH Inputs in src/input-helper.ts

The authentication process begins in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), where the action reads the workflow inputs and validates SSH-specific settings. The `getInputs()` function collects four SSH-related parameters:

- `ssh-key`: The private key content for authentication
- `ssh-known-hosts`: Optional custom host entries
- `ssh-strict`: Boolean flag for host key checking (defaults to `true`)
- `ssh-user`: Optional SSH username override

```typescript
result.sshKey = core.getInput('ssh-key')
result.sshKnownHosts = core.getInput('ssh-known-hosts')
result.sshStrict = (core.getInput('ssh-strict') || 'true').toUpperCase() === 'TRUE'
result.sshUser = core.getInput('ssh-user')

```

This input collection occurs at lines 59–65, parsing the raw workflow values into a structured settings object that subsequent modules consume.

## Secure Key Storage and Git Environment Configuration

The [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) module implements the core security logic. It transforms the input settings into temporary SSH artifacts and configures Git to use them for all remote operations.

### Writing the Private Key to Temporary Storage

To prevent credential leakage, the action writes the private key to a randomly named file within the `$RUNNER_TEMP` directory with **mode `0600`** (read/write for owner only). This ensures no other users or processes can access the key material.

```typescript
this.sshKeyPath = path.join(runnerTemp, uniqueId);
await fs.promises.writeFile(this.sshKeyPath,
                            this.settings.sshKey.trim() + '\n',
                            {mode: 0o600});

```

Lines 56–66 handle this atomic write operation, ensuring the key exists only for the duration of the job.

### Building the Known Hosts File

The action constructs a comprehensive `known_hosts` file to prevent man-in-the-middle attacks. At lines 78–99, it merges three sources:

1. The runner's existing `~/.ssh/known_hosts`
2. User-provided entries from the `ssh-known-hosts` input
3. A hard-coded entry for `github.com`

This merged file is written to `$RUNNER_TEMP` alongside the private key.

### Constructing the GIT_SSH_COMMAND

At lines 101–108, the action assembles a custom SSH command string that Git will use for all network operations. This command explicitly references the temporary key and known hosts files:

```typescript
this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"`;
if (this.settings.sshStrict) {
    this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no';
}
this.sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(this.sshKnownHostsPath)}"`;

```

When `ssh-strict` is enabled (the default), the command enforces strict host key checking while disabling IP checks to accommodate GitHub's load balancing.

### Persisting Credentials in Git Config

The action exports the SSH command through two mechanisms at lines 111–118:

```typescript
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand);
if (this.settings.persistCredentials) {
    await this.git.config(SSH_COMMAND_KEY, this.sshCommand);
}

```

First, it sets the `GIT_SSH_COMMAND` environment variable for immediate use. If `persist-credentials` is `true`, it also writes the command to the repository's local Git config under `core.sshCommand`, ensuring submodules and subsequent Git operations inherit the authentication settings.

## HTTPS Fallback When SSH Keys Are Unavailable

In [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) (lines 37–47), the action implements an authentication fallback strategy. When no `ssh-key` is provided, it configures Git to rewrite SSH URLs to HTTPS using the `insteadOf` mechanism:

```typescript
if (!this.settings.sshKey) {
    for (const insteadOfValue of this.insteadOfValues) {
        await this.git.config(this.insteadOfKey, insteadOfValue, true, true);
    }
}

```

This ensures that repositories referencing submodules or remotes via SSH still function when the workflow uses HTTPS-based authentication instead.

## Post-Job Cleanup of SSH Artifacts

After the job completes, the `GitAuthHelper.removeAuth()` method (implemented in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)) performs sanitation:

- **Removes temporary files**: Deletes the private key and `known_hosts` files from `$RUNNER_TEMP`
- **Clears Git configuration**: Removes the `core.sshCommand` entry if `persist-credentials` was enabled
- **Unsets environment variables**: Cleans up `GIT_SSH_COMMAND`

This ensures credentials do not persist on the runner for subsequent jobs.

## Implementing SSH Authentication in Workflows

To authenticate with private repositories using SSH, provide the private key through a repository secret:

```yaml

# .github/workflows/checkout-ssh.yml

name: Checkout with SSH
on: [push]

jobs:
  checkout:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout private repo via SSH
        uses: actions/checkout@v4
        with:
          repository: my-org/private-repo
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
          ssh-strict: true
          persist-credentials: true

```

For repositories with private submodules, enable recursive checkout and persist credentials:

```yaml

# .github/workflows/checkout-submodule.yml

name: Checkout with submodule SSH
on: [pull_request]

jobs:
  checkout:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
          persist-credentials: true

```

## Summary

- **Input parsing**: [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) collects `ssh-key`, `ssh-known-hosts`, `ssh-strict`, and `ssh-user` inputs from the workflow.
- **Secure storage**: [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) writes the private key to `$RUNNER_TEMP` with `0600` permissions and constructs a `known_hosts` file.
- **Git integration**: The action builds a `GIT_SSH_COMMAND` that references the temporary files and exports it to the environment and Git config.
- **Fallback behavior**: Without an SSH key, [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) configures HTTPS URL rewriting via `insteadOf` settings.
- **Cleanup**: Temporary keys and configuration entries are removed after job completion to prevent credential leakage.

## Frequently Asked Questions

### What file permissions does actions/checkout set for SSH private keys?

The action writes SSH private keys to the runner's temporary directory with **mode `0600`** (owner read/write only). This permission mask prevents group or other users from reading the key material, as implemented in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) at lines 56–66.

### Can I use actions/checkout with SSH for private submodules?

Yes. Set `submodules: recursive` or `submodules: true` in the workflow, provide the `ssh-key` input, and ensure `persist-credentials: true` is set. The persisted `core.sshCommand` configuration allows submodule initialization commands to inherit the SSH authentication settings automatically.

### How does actions/checkout handle SSH host key verification?

By default, the action enables strict host key checking (`StrictHostKeyChecking=yes`) when `ssh-strict` is true (the default). It builds a temporary `known_hosts` file in `$RUNNER_TEMP` that combines the runner's existing entries, user-provided `ssh-known-hosts` input, and a hard-coded GitHub host key entry to verify server identities.

### What happens if I don't provide an ssh-key input?

When no `ssh-key` is provided, the action skips SSH configuration and falls back to HTTPS authentication. In [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts), it configures Git URL rewriting via the `insteadOf` mechanism to translate SSH URLs to HTTPS equivalents, allowing repositories to clone using the built-in `GITHUB_TOKEN` or other HTTPS credentials.