---
title: SSH with Access for Infrastructure (recommended) · Cloudflare Zero Trust docs
description: Access for Infrastructure provides granular control over how users
  can connect to your SSH servers. This feature uses the same deployment model
  as WARP-to-Tunnel but unlocks more policy options and command logging
  functionality.
lastUpdated: 2025-10-22T21:11:06.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/
  md: https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/index.md
---

[Access for Infrastructure](https://developers.cloudflare.com/cloudflare-one/applications/non-http/infrastructure-apps/) provides granular control over how users can connect to your SSH servers. This feature uses the same deployment model as [WARP-to-Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-warp-to-tunnel/) but unlocks more policy options and command logging functionality.

Furthermore, Access for Infrastructure replaces traditional SSH keys with short-lived certificates issued to your users based on the token generated by their Access login. In traditional models, users generate an SSH key pair and administrators grant access to individual SSH servers by deploying their users' public keys to those servers. These SSH keys can remain unchanged on these servers for months or years. Cloudflare Access removes the burden of managing SSH keys, while also improving security by replacing long-lived SSH keys with ephemeral SSH certificates.

## 1. Connect the server to Cloudflare

1. In [Zero Trust](https://one.dash.cloudflare.com), go to **Networks** > **Tunnels**.

2. [Create a new tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/) or edit an existing `cloudflared` tunnel.

1) In the **CIDR** tab for the tunnel, enter the IP or CIDR address of your server. Typically this would be a private IP, but public IPs are also allowed.

## 2. Set up the client

To connect your devices to Cloudflare:

1. [Deploy the WARP client](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/deployment/) on your devices in Gateway with WARP mode.
2. [Enable the Gateway proxy for TCP](https://developers.cloudflare.com/cloudflare-one/traffic-policies/proxy/#turn-on-the-gateway-proxy).
3. [Create device enrollment rules](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/deployment/device-enrollment/) to determine which devices can enroll to your Zero Trust organization.

## 3. Route server IPs through WARP

By default, WARP excludes traffic bound for [RFC 1918 space](https://datatracker.ietf.org/doc/html/rfc1918), which are IP addresses typically used in private networks and not reachable from the Internet. In order for WARP to send traffic to your SSH server, you must configure [Split Tunnels](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/) so that the IP/CIDR of your SSH server routes through WARP.

1. First, check whether your [Split Tunnels mode](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#change-split-tunnels-mode) is set to **Exclude** or **Include** mode.

2. Edit your Split Tunnel routes depending on the mode:

   * Exclude IPs and domains

     If you are using **Exclude** mode:

     a. [Delete the route](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#remove-a-route) containing your SSH server's IP/CIDR range. For example, if your network uses the default AWS range of `172.31.0.0/16`, delete `172.16.0.0/12`.

     b. [Re-add IP/CIDR ranges](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#add-a-route) that are not explicitly used by your SSH server. For the AWS example above, you would add new entries for `172.16.0.0/13`, `172.24.0.0/14`, `172.28.0.0/15`, and `172.30.0.0/16`. This ensures that only traffic to `172.31.0.0/16` routes through WARP.

     You can use the following calculator to determine which IP addresses to re-add:

     Calculator instructions

     1. In **Base CIDR**, enter the RFC 1918 range that you deleted from Split Tunnels.
     2. In **Excluded CIDRs**, enter the IP/CIDR range used by your SSH server.
     3. Re-add the calculator results to your Split Tunnel Exclude mode list.

     By tightening the private IP range included in WARP, you reduce the risk of breaking a user's [access to local resources](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/warp-settings/#allow-users-to-enable-local-network-exclusion).

   * Include IPs and domains

     If you are using **Include** mode:

     1. Add the required [Zero Trust domains](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#cloudflare-zero-trust-domains) or [IP addresses](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#cloudflare-zero-trust-ip-addresses) to your Split Tunnel include list.
     2. [Add a route](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/configure-warp/route-traffic/split-tunnels/#add-a-route) to include your SSH server's IP/CIDR range.

## 4. Add a target

A target represents a single resource in your infrastructure (such as a server, Kubernetes cluster, database, or container) that users will connect to through Cloudflare.

Targets are protocol-agnostic, meaning that you do not need to define a new target for each protocol that runs on the server. To create a new target:

* Dashboard

  1. In [Zero Trust](https://one.dash.cloudflare.com/), go to **Networks** > **Targets**.

  2. Select **Add a target**.

  3. In **Target hostname**, enter a user-friendly name for the target. We recommend using the server hostname, for example `production-server`. The target hostname does not need to be unique and can be reused for multiple targets. Hostnames are used to define the targets secured by an Access application; they are not used for DNS address resolution.

     Hostname format restrictions

     * Case insensitive
     * Contain no more than 253 characters
     * Contain only alphanumeric characters, `-`, or `.` (no spaces allowed)
     * Start and end with an alphanumeric character

  4. In **IP addresses**, enter the IPv4 and/or IPv6 address of the target resource. The dropdown menu will not populate until you type in the full IP address.

  Note

  If the target IP does not appear in the dropdown, go to **Networks** > **Routes** and confirm that the IP routes through Cloudflare Tunnel.

  1. In the dropdown menu, select the IP address and [virtual network](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/private-net/cloudflared/tunnel-virtual-networks/) where the resource is located. This IP address and virtual network pairing is now assigned to this target and cannot be reused in another target by design.
  2. Select **Add target**.

* API

  Make a `POST` request to the [Infrastructure Access Targets](https://developers.cloudflare.com/api/resources/zero_trust/subresources/access/subresources/infrastructure/subresources/targets/methods/create/) endpoint:

  ```bash
  curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/infrastructure/targets" \
    --request POST \
    --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    --json '{
      "hostname": "infra-access-target",
      "ip": {
          "ipv4": {
              "ip_addr": "187.26.29.249",
              "virtual_network_id": "c77b744e-acc8-428f-9257-6878c046ed55"
          },
          "ipv6": {
              "ip_addr": "64c0:64e8:f0b4:8dbf:7104:72b0:ec8f:f5e0",
              "virtual_network_id": "c77b744e-acc8-428f-9257-6878c046ed55"
          }
      }
    }'
  ```

* Terraform

  Provider versions

  The following example requires Cloudflare provider version `>=4.45.0`.

  1. Add the following permission to your [`cloudflare_api_token`](https://registry.terraform.io/providers/cloudflare/cloudflare/4.45.0/docs/resources/api_token):

     * `Zero Trust Write`

  2. Configure the [`cloudflare_zero_trust_infrastructure_access_target`](https://registry.terraform.io/providers/cloudflare/cloudflare/4.45.0/docs/resources/zero_trust_infrastructure_access_target) resource:

     ```tf
     resource "cloudflare_zero_trust_infrastructure_access_target" "infra-ssh-target" {
       account_id = var.cloudflare_account_id
         hostname   = "infra-access-target"
         ip = {
           ipv4 = {
             ip_addr = "187.26.29.249"
             virtual_network_id = "c77b744e-acc8-428f-9257-6878c046ed55"
           }
           ipv6 = {
             ip_addr = "64c0:64e8:f0b4:8dbf:7104:72b0:ec8f:f5e0"
             virtual_network_id = "c77b744e-acc8-428f-9257-6878c046ed55"
           }
         }
     }
     ```

Next, create an Access application to secure the target.

## 5. Add an infrastructure application

* Dashboard

  1. In [Zero Trust](https://one.dash.cloudflare.com/), go to **Access** > **Applications**.

  2. Select **Add an application**.

  3. Select **Infrastructure**.

  4. Enter any name for the application.

  5. In **Target criteria**, select the target hostname(s) that you want to secure. This application definition will apply to all targets that share the selected hostname, including any targets added in the future. Similarly, if you later decide to change the hostname for a target, the renamed target will no longer be covered by this application.

  6. Enter the **Protocol** and **Port** that will be used to connect to the server.

  7. (Optional) If a protocol runs on more than one port, select **Add new target criteria** and reconfigure the same target hostname and protocol with a different port number.

     Note

     Access for Infrastructure only supports assigning one protocol per port. You can reuse a port/protocol pairing across infrastructure applications, but the port cannot be reassigned to another protocol.

  8. Select **Next**.

  9. To secure your targets, configure a policy that defines who can connect and how they can connect:

     1. Enter any name for your policy.

     2. Create a rule that matches the users who are allowed to reach the targets. For more information, refer to [Access policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/) and review the list of [infrastructure policy selectors](https://developers.cloudflare.com/cloudflare-one/applications/non-http/infrastructure-apps/#infrastructure-policy-selectors).

     3. In **Connection context**, configure the following settings:

        * **SSH user**: Enter the UNIX usernames that users can log in as (for example, `root` or `ec2-user`).
        * **Allow users to log in as their email alias**: (Optional) When selected, users who match your policy definition will be able to access the target using their lowercased email address prefix. For example, `Jdoe@company.com` could log in as `jdoe`.

        Note

        Cloudflare will not create new users on the target. UNIX users must already be present on the server.

  10. Select **Add application**.

* API

  Make a `POST` request to the [Access applications](https://developers.cloudflare.com/api/resources/zero_trust/subresources/access/subresources/applications/methods/create/) endpoint:

  Required API token permissions

  At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

  * `Access: Apps and Policies Write`

  ```bash
  curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/apps" \
    --request POST \
    --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    --json '{
      "name": "Example infrastructure app",
      "type": "infrastructure",
      "target_criteria": [
          {
              "target_attributes": {
                  "hostname": [
                      "infra-access-target"
                  ]
              },
              "port": 22,
              "protocol": "SSH"
          }
      ],
      "policies": [
          {
              "name": "Allow a specific email",
              "decision": "allow",
              "include": [
                  {
                      "email": {
                          "email": "jdoe@company.com"
                      }
                  }
              ],
              "connection_rules": {
                  "ssh": {
                      "usernames": [
                          "root",
                          "ec2-user"
                      ]
                  }
              }
          }
      ]
    }'
  ```

* Terraform (v4)

  Provider versions

  The following example requires Cloudflare provider version `>=4.45.0`.

  1. Add the following permission to your [`cloudflare_api_token`](https://registry.terraform.io/providers/cloudflare/cloudflare/4.45.0/docs/resources/api_token):

     * `Access: Apps and Policies Write`

  2. Use the [`cloudflare_zero_trust_access_application`](https://registry.terraform.io/providers/cloudflare/cloudflare/4.45.0/docs/resources/zero_trust_access_application) resource to create an infrastructure application:

     ```tf
     resource "cloudflare_zero_trust_access_application" "infra-app" {
       account_id = var.cloudflare_account_id
       name       = "Example infrastructure app"
       type       = "infrastructure"


       target_criteria {
         port     = 22
         protocol = "SSH"
         target_attributes {
           name = "hostname"
           values = ["infra-access-target"]
         }
       }
     }
     ```

  3. Use the [`cloudflare_zero_trust_access_policy`](https://registry.terraform.io/providers/cloudflare/cloudflare/4.45.0/docs/resources/zero_trust_access_policy) resource to add an infrastructure policy to the application:

     ```tf
     resource "cloudflare_zero_trust_access_policy" "infra-app-policy" {
       application_id = cloudflare_zero_trust_access_application.infra-app.id
       account_id = var.cloudflare_account_id
       name       = "Allow a specific email"
       decision   = "allow"
       precedence = 1


       include {
         email = ["jdoe@company.com"]
       }


       connection_rules {
         ssh {
           usernames = ["root", "ec2-user"]
         }
       }
     }
     ```

The targets in this application are now secured by your infrastructure policies.

## 6. (Recommended) Modify order of precedence in Gateway

By default, Cloudflare will evaluate Access application policies after evaluating all Gateway network policies. To evaluate Access applications before or after specific Gateway policies:

1. Create the following [Gateway network policy](https://developers.cloudflare.com/cloudflare-one/traffic-policies/network-policies/):

   | Selector | Operator | Value | Action |
   | - | - | - | - |
   | Access Infrastructure Target | is | *Present* | Allow |

2. Update the policy's [order of precedence](https://developers.cloudflare.com/cloudflare-one/traffic-policies/order-of-enforcement/#order-of-precedence) using the dashboard or API.

This Gateway policy will apply to all Access for Infrastructure targets, including RDP and SSH.

Note

Users must pass the policies in your Access application before they are granted access. The Gateway Allow policy is strictly for routing and connectivity purposes.

## 7. Configure SSH server

Next, configure your SSH server to trust the Cloudflare SSH CA. This allows Access to authenticate using short-lived certificates instead of traditional SSH keys.

### Generate a Cloudflare SSH CA

Note

Other short-lived CAs, such as those used to [secure SSH servers behind Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/applications/non-http/short-lived-certificates-legacy/), are incompatible with the Gateway SSH proxy. For SSH logging to work, you must create a new CA using the `gateway_ca` API endpoint.

To generate a Cloudflare SSH CA and get its public key:

1. [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with the following permissions:

   | Type | Item | Permission |
   | - | - | - |
   | Account | Access: SSH Auditing | Edit |

2. If you have not yet generated a Cloudflare SSH CA, make a `POST` request to the Cloudflare API:

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Access: SSH Auditing Write`

```bash
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/gateway_ca" \
  --request POST \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
```

1. If you have already created a Cloudflare SSH CA or receive the error message `access.api.error.gateway_ca_already_exists`, make a `GET` request instead:

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Access: SSH Auditing Write`
* `Access: SSH Auditing Read`

```bash
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/gateway_ca" \
  --request GET \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
```

1. Copy the `public_key` value returned in the response.

### Save the public key

1. Use the following command to change directories to the SSH configuration directory on the remote target machine:

   ```sh
   cd /etc/ssh
   ```

2. Once there, you can use the following command to both generate the file and open a text editor to input/paste the public key.

   ```sh
   vim ca.pub
   ```

3. In the `ca.pub` file, paste the public key without any modifications.

   ```txt
   ecdsa-sha2-nistp256 <redacted> open-ssh-ca@cloudflareaccess.org
   ```

   The `ca.pub` file can hold multiple keys, listed one per line. Empty lines and comments starting with `#` are also allowed.

4. Save the `ca.pub` file. In some systems, you may need to use the following command to force the file to save depending on your permissions:

   ```bash
   :w !sudo tee %
   :q!
   ```

### Modify your `sshd_config` file

Configure your SSH server to trust the Cloudflare SSH CA by updating the `sshd_config` file on the remote target machine.

1. While in the `/etc/ssh` directory on the remote machine, open the `sshd_config` file.

   ```sh
    sudo vim /etc/ssh/sshd_config
   ```

2. Press `i` to enter insert mode, then add the following lines at the top of the file, above all other directives:

   ```txt
   PubkeyAuthentication yes
   TrustedUserCAKeys /etc/ssh/ca.pub
   ```

   Be aware of your include statements

   If there are any include statements below these lines, the configurations in those files will not take precedence.

3. Press `esc` and then type `:x` and press `Enter` to save and exit.

Note

For certain distributions, such as Amazon Linux 1 (based on RHEL), the certificate file permissions must be set to `600`. You can set file permissions with the following command:

```sh
chmod 600 /etc/ssh/ca.pub
```

### Reload your SSH server

Once you have modified your `sshd` configuration, reload the SSH service on the remote machine for the changes to take effect.

* Debian/Ubuntu

  For Debian/Ubuntu:

  ```sh
  sudo systemctl reload ssh
  ```

* CentOS/RHEL

  For CentOS/RHEL 7 and newer:

  ```sh
  sudo systemctl reload sshd
  ```

## 8. Connect as a user

Users can use any SSH client to connect to the target, as long as they are logged into the WARP client on their device. If the target is located within a particular virtual network, ensure that the WARP client is [connected to that virtual network](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/private-net/cloudflared/tunnel-virtual-networks/#connect-to-a-virtual-network) before initiating the connection. Users do not need to modify any SSH configs on their device. For example, to SSH from a terminal:

```sh
ssh <username>@<target IP>
```

Access for Infrastructure also supports `scp`, `sftp`, and `rsync` commands. Refer to [Known limitations](#known-limitations) for a list of unsupported SSH commands and features.

To learn more about user connections, refer to the [Access for Infrastructure documentation](https://developers.cloudflare.com/cloudflare-one/applications/non-http/infrastructure-apps/#4-connect-as-a-user).

## SSH command logs

SSH command logs contain the actual SSH commands that a user ran on the target. Customers on all plans can store SSH logs on Cloudflare and download the logs from the dashboard. [Downloadable logs](#download-encrypted-ssh-logs) are encrypted using a public key provided by the customer and are not visible to Cloudflare. Delivery of downloadable SSH logs is best effort; for guaranteed delivery, Enterprise customers can [configure a Logpush job](#export-ssh-logs-with-logpush) to send SSH logs to storage destinations. Logpush payloads are not encrypted with a customer-provided public key.

### Download encrypted SSH logs

Follow these instructions to encrypt and download SSH command logs from Zero Trust.

#### Enable SSH command logging

To log SSH commands, you will need to generate an HPKE key pair and upload the public key to Cloudflare.

1. [Download](https://github.com/cloudflare/ssh-log-cli/releases/latest/) the Cloudflare `ssh-log-cli` utility.

2. Using the `ssh-log-cli` utility, generate a public and private key pair.

   ```sh
   ./ssh-log-cli generate-key-pair -o sshkey
   ls
   ```

   ```sh
   README.md    ssh-log-cli    sshkey    sshkey.pub
   ```

   This command outputs two files, an `sshkey.pub` public key and a matching `sshkey` private key.

3. In [Zero Trust](https://one.dash.cloudflare.com), go to **Settings** > **Network**.

4. In **SSH encryption public key**, paste the contents of `sshkey.pub` and select **Save**.

All proxied SSH commands are immediately encrypted using this public key. The matching private key is required to view logs.

#### Disable SSH command logging

To turn off SSH command logging, delete your uploaded public key:

* Dashboard

  1. In [Zero Trust](https://one.dash.cloudflare.com), go to **Settings** > **Network** > **SSH encryption public key**.

  2. Select **Remove**.

  3. Select **Remove key** to confirm.

  Cloudflare will stop logging SSH commands to your targets, as well as any commands subject to [Gateway Audit SSH](https://developers.cloudflare.com/cloudflare-one/traffic-policies/network-policies/ssh-logging/) policies.

* API

  To delete the SSH encryption public key using the [API](https://developers.cloudflare.com/api/resources/zero_trust/subresources/gateway/subresources/audit_ssh_settings/methods/update/):

  Required API token permissions

  At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

  * `Zero Trust Write`

  ```bash
  curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/audit_ssh_settings" \
    --request PUT \
    --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
    --json '{
      "public_key": ""
    }'
  ```

#### View SSH logs

SSH command logs are not visible from the dashboard itself and must be exported and decrypted.

To manually retrieve logs:

1. In [Zero Trust](https://one.dash.cloudflare.com), go to **Logs** > **Access**.

2. Filter the logs using the name of your [SSH application](#5-add-an-infrastructure-application).

3. Select the SSH session for which you want to export command logs.

4. In the side panel, scroll down to **SSH logs** and select **Download**.

5. To decrypt the log, follow the instructions in the [SSH Logging CLI repository](https://github.com/cloudflare/ssh-log-cli/). In the following example, `sshkey` is the private key that matches the public key uploaded to Cloudflare.

   ```sh
   ./ssh-log-cli decrypt -i sshlog -k sshkey
   ```

   This command outputs a `sshlog-decrypted.zip` file with the decrypted logs.

### Export SSH logs with Logpush

Availability

Only available on Enterprise plans.

Cloudflare allows you to send SSH command logs to storage destinations configured in [Logpush](https://developers.cloudflare.com/logs/logpush/), including third-party destinations. For a list of available data fields, refer to the [SSH logs dataset](https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/account/ssh_logs/).

To set up the Logpush job, refer to [Logpush integration](https://developers.cloudflare.com/cloudflare-one/insights/logs/logpush/).

## Known limitations

### SSH features

The following SSH features are not supported:

* Local and remote port forwarding
* SSH agent forwarding
* X11 forwarding

### Session duration

SSH sessions have a maximum expected duration of 10 hours. For more information, refer to the [Troubleshooting FAQ](https://developers.cloudflare.com/cloudflare-one/faq/troubleshooting/#long-lived-ssh-sessions-frequently-disconnect).

## Troubleshooting

Failure to connect to your SSH endpoint could be the result of multiple variables. Use the following steps to investigate and resolve the source of your connection failure.

1. [Verify that your Access policies](#1-review-access-policies) allow the user to access the target.
2. [Check Cloudflare Tunnel](#2-check-target-machine-connection) health.
3. [Confirm user existence](#3-confirm-user-existence-on-the-target-server) on the server.
4. [Check your `sshd_config` file](#4-debug-sshd_config-file-misconfiguration) for misconfiguration.

### 1. Review Access policies

A user may be blocked by an Access policy from reaching your server because no explicit allow Access policy exists and Access is set to deny the user by default.

Access policies and infrastructure applications

The Access infrastructure application (created in [step 5](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#5-add-an-infrastructure-application)) is the policy container for your SSH server. Cloudflare refers to your server that you connect to with SSH as a [target](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#4-add-a-target).

[Access policies](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/policy-management/) are the rules attached to this Access infrastructure application, determining who can connect and what UNIX usernames they can log in as on the server. Cloudflare will not create new users on the target. UNIX users must already be present on the server.

You were guided to create an Access policy for your target in [substep 9 of step 5: Add an infrastructure application](#5-add-an-infrastructure-application).

#### End users

As an end user, run [`warp-cli target list`](https://developers.cloudflare.com/cloudflare-one/applications/non-http/infrastructure-apps/#display-available-targets) to verify that you have access to the target.

```sh
warp-cli target list
```

```sh
╭──────────────────────────────────────┬──────────┬───────┬───────────────────────┬──────────────────────┬────────────╮
│ Target ID                            │ Protocol │ Port  │ Attributes            │ IP (Virtual Network) │ Usernames  │
├──────────────────────────────────────┼──────────┼───────┼───────────────────────┼──────────────────────┼────────────┤
│ 0193f22a-9df3-78e3-b5bb-7ab631903306 │ SSH      │ 22    │ hostname: do-target   │ 10.116.0.3 (a1net)   │ alice      │
├──────────────────────────────────────┼──────────┼───────┼───────────────────────┼──────────────────────┼────────────┤
│ 0193f22a-9df3-78e3-b5bb-7ab631903306 │ SSH      │ 23    │ hostname: do-target   │ 10.116.0.3 (a1net)   │ root       │
├──────────────────────────────────────┼──────────┼───────┼───────────────────────┼──────────────────────┼────────────┤
│ 01943cff-6130-7989-8bff-cbc02b59a2b1 │ SSH      │ 80    │ hostname: az-target   │ 172.16.0.0 (b1net)   │ alice, bob │
╰──────────────────────────────────────┴──────────┴───────┴───────────────────────┴──────────────────────┴────────────╯
```

* If the target appears in the list, confirm that the username you are attempting to connect with is shown in the output. If the username is not shown, an administrator must find the Access policy associated with the target and add that username to the Access policy. An administrator should have created an Access policy in [substep 9 of step 5: Add an infrastructure application](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#5-add-an-infrastructure-application). If the username is shown, that means the Access policy should be granting access and you should ensure that the tunnel is healthy in [step 2](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#2-check-target-machine-connection).

* If the target does not appear in the list, an administrator must audit the Access policies for the target in the Zero Trust dashboard for potential misconfiguration that may be blocking connection.

#### Administrators

As an admin, instead of running `warp-cli target list` on the end user device, you can use the Access logs to review if an Access policy is causing connection issues. Reviewing logs is useful when troubleshooting connection issues on behalf of the end user.

Note

You will need Cloudflare dashboard access and log view [permissions](https://developers.cloudflare.com/cloudflare-one/roles-permissions/) to proceed with this step.

1. In [Zero Trust](https://one.dash.cloudflare.com/), go to **Logs** > **Access**.

2. Select the application you are testing or filter *Infrastructure* as the App Type.

3. Review the **Decision**. If the **Decision** is `Access denied`, select the application and copy the name under App.

   If the decision is `Access granted`, Access policies are not interfering with your connection attempts and your connection issue is due to the Cloudflare Tunnel ([step 2](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#2-check-target-machine-connection)), the SSH server ([step 3](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#3-confirm-user-existence-on-the-target-server)), or the `sshd_config` file ([step 4](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#4-debug-sshd_config-file-misconfiguration)).

4. Go to **Access** > **Applications**.

5. Input the app name in the search bar and select the application.

6. Select **Configure**.

7. Go to [**Policies**](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/policy-management/#test-your-policies) to review what criteria may be blocking the user.

By adding an Access [policy](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/) to allow the user, the connection issue should be resolved. After saving your policy changes, attempt to connect to the server.

If you are still having connection issues after auditing your Access policies, review tunnel health in the following step.

### 2. Check target connection

If the end user cannot connect to the target, the tunnel you set up in [step 1: Connect the server to Cloudflare](#1-connect-the-server-to-cloudflare) may be down or inactive.

To check the status of your tunnel:

1. In [Zero Trust](https://one.dash.cloudflare.com/), go to **Networks** > **Routes**.

2. Search your IP to find the tunnel associated with the IP.

   This IP will be visible in the `warp-cli target list` output in [the previous step](#1-review-access-policies). If you are an admin, you can also go to **Networks** > **Targets** and find the IP next to your Hostname.

3. Copy the tunnel name.

4. Go to **Networks** > **Tunnels** and search by your tunnel name.

5. Review that the [Tunnel status](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/monitor-tunnels/notifications/#available-notifications) says `Active`, and not `Down`, `Degraded`, or `Inactive`.

| Status | Meaning | Recommended Action |
| - | - | - |
| **Healthy** | The tunnel is active and serving traffic through four connections to the Cloudflare global network. | No action is required. Your tunnel is running correctly. |
| **Inactive** | The tunnel has been created (via the API or dashboard) but the `cloudflared` connector has never been run to establish a connection. | Run the tunnel as a service (recommended) or use the `cloudflared tunnel run` command on your origin server to connect the tunnel to Cloudflare. Refer to [substep 6 of step 1 in the Create a Tunnel dashboard guide](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/#1-create-a-tunnel) or step 4 in the [Create a Tunnel API guide](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/#1-create-a-tunnel). |
| **Down** | The tunnel was previously connected but is currently disconnected because the `cloudflared` process has stopped. | 1. Ensure the `cloudflared` [service](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/local-management/as-a-service/) or process is actively running on your server. 2. Check for server-side issues, such as the machine being powered off, an application crash, or recent network changes. |
| **Degraded** | The `cloudflared` connector is running and the tunnel is serving traffic, but at least one individual connection has failed. Further degradation in [tunnel availability](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/tunnel-availability/) could risk the tunnel going down and failing to serve traffic. | 1. Review your `cloudflared` [logs](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/monitor-tunnels/logs/) for connection failures or error messages. 2. Investigate local network and firewall rules to ensure they are not blocking connections to the [Cloudflare Tunnel IPs and ports](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/tunnel-with-firewall/).  |

For detailed steps on troubleshooting, refer to the [Troubleshooting Tunnel documentation](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/troubleshoot-tunnels/). Review the [Tunnel with Firewall documentation](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/tunnel-with-firewall/#test-connectivity) to ensure your network is correctly configured to allow `cloudflared` connections.

After you have verified that there are no issues with your tunnel's health, confirm the user's existence on the server in the following step.

### 3. Confirm user existence on the server

To verify the existence of the end user on the server, run the `id <USERNAME>` command on the server to verify that the end user's username exists. If the username does not exist, you must add the user to the server.

If the user exists on the server, debug your `sshd_config` file in the following step.

### 4. Debug `sshd_config` file misconfiguration

One reason a user is failing to connect to your SSH endpoint might be the result of a misconfigured `sshd_config` file. Follow the steps below to audit your `sshd_config` file for misconfigurations.

#### Review your `sshd` logs

`sshd` logs can confirm whether or not the user is making it to the server. The location of your `sshd` logs is defined in your `sshd_config`. The logs location is likely at `journalctl -u ssh` on Ubuntu and `tail /var/log/auth.log` for Red Hat.

Using your `sshd` logs, validate that SSH connection attempts are arriving to the server.

#### Review your `sshd_config` file for misconfigurations

To rule out any issues in your `sshd_config` file, compare your existing `sshd_config` file with the example below to verify if any directives are causing authentication issues. The following example `sshd_config` file will result in successful authentication:

Example `sshd_config` file

```plaintext
# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.


# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented.  Uncommented options override the
# default value.


PubkeyAuthentication yes
TrustedUserCAKeys /etc/ssh/ca.pub


Include /etc/ssh/sshd_config.d/*.conf


# When systemd socket activation is used (the default), the socket
# configuration must be re-generated after changing Port, AddressFamily, or
# ListenAddress.
#
# For changes to take effect, run:
#
#   systemctl daemon-reload
#   systemctl restart ssh.socket
#
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::


#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key


# Ciphers and keying
#RekeyLimit default none


# Logging
#SyslogFacility AUTH
LogLevel DEBUG3


# Authentication:


#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10






# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile    .ssh/authorized_keys .ssh/authorized_keys2


#AuthorizedPrincipalsFile none


#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody


# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes


# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
#PermitEmptyPasswords no


# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
KbdInteractiveAuthentication no


# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no


# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no


# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the KbdInteractiveAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via KbdInteractiveAuthentication may bypass
# the setting of "PermitRootLogin yes
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and KbdInteractiveAuthentication to 'no'.
UsePAM yes


#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none


# no default banner path
#Banner none


# Allow client to pass locale environment variables
AcceptEnv LANG LC_*


# override default of no subsystems
Subsystem    sftp    /usr/lib/openssh/sftp-server


# Example of overriding settings on a per-user basis
#Match User anoncvs
#    X11Forwarding no
#    AllowTcpForwarding no
#    PermitTTY no
#    ForceCommand cvs server
```

#### Replace and test with example configuration

The next steps will walk you through a troubleshooting regimen. You will temporarily replace your existing `sshd_config` file with the provided example to rule out configuration issues. Before proceeding, carefully [review and compare both files](#review-your-sshd_config-file-for-misconfigurations) to identify any conflicting directives.

You may lose access to your server

These troubleshooting steps could result in you being locked out of your SSH server because your current SSH session may rely on existing configuration that is not in the [example file](#review-your-sshd_config-file-for-misconfigurations). Proceed with utmost caution.

1. Back up the existing `sshd_config` file.

   ```sh
   mv /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
   ```

2. Create a new `sshd_config` file.

   ```sh
   vi /etc/ssh/sshd_config
   ```

3. Enter insert mode by pressing the `i` key on your keyboard.

4. Paste in the [example file](#review-your-sshd_config-file-for-misconfigurations).

5. Exit insert mode by pressing the escape (`esc`) key.

6. Enter `:x` to save and exit.

7. [Reload](#reload-your-ssh-server) your SSH server.

   Do not restart

   Restarting your `sshd` service will result in the termination of your current SSH connection. Make sure to reload instead of restarting to avoid terminating all currently open SSH sessions.

   Once you have modified your `sshd` configuration, reload the SSH service on the remote machine for the changes to take effect.

   * Debian/Ubuntu

     For Debian/Ubuntu:

     ```sh
     sudo systemctl reload ssh
     ```

   * CentOS/RHEL

     For CentOS/RHEL 7 and newer:

     ```sh
     sudo systemctl reload sshd
     ```

By completing all four troubleshooting steps, you should have resolved any connection issues caused by misconfiguration of the SSH server. If issues persist, [recheck `sshd` logs](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#review-your-sshd-logs). The example [`sshd_config` shared above](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#review-your-sshd_config-file-for-misconfigurations) enables debug logging and may expose more specific issues.
