---
source_url: "https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/"
title: SSH with Access for Infrastructure · Cloudflare One docs
mirrored_at: 2026-08-08T03:38:14.465Z
host: developers.cloudflare.com
cited_in_42a: true
mirror_canonical: "https://index.42a.ai/developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/index"
---

> **Original source:** https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/

[Access for Infrastructure](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/non-http/infrastructure-apps/) provides granular control over how users can connect to your SSH servers. Like the [self-managed SSH keys](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-device-client/) method, it uses the Cloudflare One Client on user devices and Cloudflare Tunnel on the server to create a secure, private connection through Cloudflare's network. Access for Infrastructure adds application-level policies with per-target and per-username controls, as well as SSH command logging.

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.  In the Cloudflare dashboard, go to **Networking** > **Tunnels**.
    
    [Go to **Tunnels** ↗](https://dash.cloudflare.com/?to=/:account/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.
    

3.  In the Cloudflare dashboard, go to **Networking** > **Routes**.
    
    [Go to **Routes** ↗](https://dash.cloudflare.com/?to=/:account/magic-networks/routes)
4.  Select **Create route** > **Tunnel CIDR**. Select the tunnel you just created, enter the IP or CIDR address of your server (typically a private IP, but public IPs are also allowed), and select **Create route**.
    

To connect your devices to Cloudflare:

1.  [Deploy the Cloudflare One Client](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/deployment/) on your devices in Traffic and DNS 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/cloudflare-one-client/deployment/device-enrollment/) to determine which devices can enroll to your Zero Trust organization.

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 the Cloudflare One Client to send traffic to your SSH server, you must configure [Split Tunnels](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/configure/route-traffic/split-tunnels/) so that the IP/CIDR of your SSH server routes through the Cloudflare One Client.

1.  First, check whether your [Split Tunnels mode](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/configure/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:
    
    If you are using **Exclude** mode:
    
    a. [Delete the route](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/configure/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/cloudflare-one-client/configure/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 the Cloudflare One Client.
    
    You can use the following calculator to determine which IP addresses to re-add:
    
    Base CIDRSubtracted CIDRs
    
    Calculator instructions
    
    1.  In **Base CIDR**, enter the RFC 1918 range that you deleted from Split Tunnels.
    2.  In **Subtracted 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 the Cloudflare One Client, you reduce the risk of breaking a user's [access to local resources](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/configure/settings/#allow-users-to-enable-local-network-exclusion).
    

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:

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Access controls** > **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.

5.  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.
6.  Select **Add target**.

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:

Create new targetbash

```
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"
				}
		}
	}'
```

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:
    
    ```
    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.

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Access controls** > **Applications**.
    
2.  Select **Create new 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.
    
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/access-controls/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`.
10.  Select **Add application**.
     

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`

Add an Access applicationbash

```
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"
										]
								}
						}
				}
		]
	}'
```

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:
    
    ```
    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:
    
    ```
    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.

Traffic from the Cloudflare One Client to your infrastructure targets is filtered by both [Gateway network policies](https://developers.cloudflare.com/cloudflare-one/traffic-policies/get-started/network/) and the application-specific Access policies.

To prevent Cloudflare One Client users from accessing your entire private network, we recommend creating a [catch-all Gateway block policy](https://developers.cloudflare.com/learning-paths/replace-vpn/build-policies/create-policy/#catch-all-policy) for your private IP space. You can then layer on higher priority Allow policies (in either Access or Gateway) which grant users access to specific applications or IPs.

By default, Cloudflare will evaluate Access application policies after evaluating all [Gateway network policies](https://developers.cloudflare.com/cloudflare-one/traffic-policies/network-policies/). To evaluate Access applications before or after specific Gateway policies:

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Traffic policies** > **Firewall policies**. In **Network**, [create a Network policy](https://developers.cloudflare.com/cloudflare-one/traffic-policies/network-policies/) with the following configuration:
    
    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.

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.

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

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Access controls** > **Service credentials** > **SSH**.
    
2.  Select **Add a certificate**.
    
3.  Under **SSH with Access for Infrastructure**, select **Generate SSH CA**. A new row will appear in the short-lived certificates table called **SSH with Access for Infrastructure**.
    
4.  Select the **SSH with Access for Infrastructure** certificate.
    
5.  Copy its **CA public key**. You can return to copy this public key at any time.
    

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`

Add a new SSH Certificate Authority (CA)bash

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

3.  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`

List SSH Certificate Authorities (CA)bash

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

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

1.  Use the following command to change directories to the SSH configuration directory on the remote target machine:
    
    ```
    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.
    
    ```
    vim ca.pub
    ```
    
3.  In the `ca.pub` file, paste the public key without any modifications.
    
    ca.pubtxt
    
    ```
    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:
    
    ```
    :w !sudo tee %
    :q!
    ```
    

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.
    
    ```
     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:
    
    ```
    PubkeyAuthentication yes
    TrustedUserCAKeys /etc/ssh/ca.pub
    ```
    
3.  Press `esc` and then type `:x` and press `Enter` to save and exit.
    

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

For Debian/Ubuntu:

```
sudo systemctl reload ssh
```

For CentOS/RHEL 7 and newer:

```
sudo systemctl reload sshd
```

You can require users to authenticate with a [YubiKey PIV key](https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/independent-mfa/#enroll-a-piv-key-for-infrastructure-apps) before connecting to your SSH servers. When MFA is required, users must complete public key authentication with their enrolled PIV key before the connection is established.

To configure independent MFA for SSH, refer to [Enforce MFA for infrastructure applications](https://developers.cloudflare.com/cloudflare-one/access-controls/policies/mfa-requirements/#infrastructure-applications).

Before users can connect with MFA enabled, they must:

1.  [Enroll a PIV key](https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/independent-mfa/#enroll-a-piv-key-for-infrastructure-apps) through the App Launcher.
2.  [Configure their SSH client](https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/independent-mfa/#configure-your-ssh-client) to use the enrolled PIV key.

When the user runs `ssh <username>@<target IP>`, the SSH proxy checks if MFA is required for the matching policy. If required, the user must touch their YubiKey and enter their PIN (depending on the key's PIN policy). The proxy then completes the connection.

Users can use any SSH client to connect to the target, as long as they are logged into the Cloudflare One Client on their device. If the target is located within a particular virtual network, ensure that the Cloudflare One 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:

```
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/access-controls/applications/non-http/infrastructure-apps/#4-connect-as-a-user).

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.

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

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.
    
    ```
    ./ssh-log-cli generate-key-pair -o sshkey
    ls
    ```
    
    ```
    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 the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Traffic policies** > **Traffic settings**.
    
4.  In **SSH log 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.

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

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Traffic policies** > **Traffic settings** > **SSH log 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.

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/):

Update Zero Trust SSH settingsbash

```
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": ""
	}'
```

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

To manually retrieve logs:

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Insights** > **Logs**.
2.  Select **SSH command logs**.
3.  Filter the logs using the name of your [SSH application](#5-add-an-infrastructure-application).
4.  Select the SSH session for which you want to export command logs.
5.  In the side panel, scroll down to **SSH logs** and select **Download**.
6.  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.
    
    ```
    ./ssh-log-cli decrypt -i sshlog -k sshkey
    ```
    
    This command outputs a `sshlog-decrypted.zip` file with the decrypted logs.
    

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/).

The following SSH features are not supported:

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

SSH sessions have a maximum expected duration of 10 hours. For more information, refer to [Troubleshoot Access](https://developers.cloudflare.com/cloudflare-one/access-controls/troubleshooting/#long-lived-ssh-sessions-disconnect).

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.

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.

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

```
warp-cli target list
```

```
╭──────────────────────────────────────┬──────────┬───────┬───────────────────────┬──────────────────────┬────────────╮
│ 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 Cloudflare One for potential misconfiguration that may be blocking connection.
    

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.

1.  In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Zero Trust** > **Insights** > **Logs**.
    
2.  Select **Access authentication logs**.
    
3.  Select the application you are testing or filter _Infrastructure_ as the App Type.
    
4.  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)).
    
5.  Go to **Access controls** > **Applications**.
    
6.  Input the app name in the search bar and select the application.
    
7.  Select **Configure**.
    
8.  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.

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 the Cloudflare dashboard, go to **Networking** > **Routes**.
    
    [Go to **Routes** ↗](https://dash.cloudflare.com/?to=/:account/magic-networks/routes)
2.  Search your IP to find the route and its associated tunnel.
    
    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.  Select the tunnel name in the **Connector** column to open the tunnel detail page.
    
4.  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.

Install and run `cloudflared` on your origin server to connect the tunnel to Cloudflare. You can find the installation command in the Cloudflare dashboard under **Networking** > **Tunnels** — select your tunnel, then on the **Overview** tab select **Add a replica**. For API-based setup, refer to [Install and run the tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel-api/#4-install-and-run-the-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.

To verify the existence of a user on a UNIX server, run the `id <USERNAME>` command on the server to verify that the 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.

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.

`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.

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

```
# 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
```

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.

1.  Back up the existing `sshd_config` file.
    
    ```
    mv /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
    ```
    
2.  Create a new `sshd_config` file.
    
    ```
    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.
    
    Once you have modified your `sshd` configuration, reload the SSH service on the remote machine for the changes to take effect.
    
    For Debian/Ubuntu:
    
    ```
    sudo systemctl reload ssh
    ```
    
    For CentOS/RHEL 7 and newer:
    
    ```
    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.

For the fastest possible troubleshooting, ensure your support ticket includes comprehensive details. The more context you provide, the faster your issue can be identified and resolved.

To ensure efficient resolution when [contacting support](https://developers.cloudflare.com/support/contacting-cloudflare-support/), include as much relevant detail as possible in your ticket:

-   Context: Briefly describe the scenario or use case (for example, where the user was, what they were trying to do).
    
-   Reproduction steps: Describe the steps you took to reproduce the issue during troubleshhooting.
    
-   Timestamps: Be specific and include the exact time and time zone when the issue occurred.
    
-   Troubleshooting attempts: Outline any troubleshooting steps or changes already attempted to resolve the issue.
    
-   `sshd` debug-level logs: Attach the `sshd` logs you collected in [step 4: Debug `sshd_config` file misconfiguration](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/use-cases/ssh/ssh-infrastructure-access/#review-your-sshd-logs).
-   `sshd_config` file: Include a copy of your server's `sshd_config` to help identify any misconfigurations or conflicting directives.
-   Client-side SSH output: Run the failing SSH command with verbose flags (`-vvv`) and include the full terminal output to show connection and authentication attempts from the client side.