[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/connections/connect-networks/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 keypair 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. Create a Cloudflare Tunnel for your server by following our [dashboard setup guide](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/). You can skip the [connect an application step](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/#2a-connect-an-application) and go straight to connecting a network.

1) In the **Private Networks** 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/connections/connect-devices/warp/deployment/) on your devices in Gateway with WARP mode.
2. [Enable the Gateway proxy for TCP](https://developers.cloudflare.com/cloudflare-one/policies/gateway/proxy/#turn-on-the-gateway-proxy).
3. [Create device enrollment rules](https://developers.cloudflare.com/cloudflare-one/connections/connect-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/connections/connect-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/connections/connect-devices/warp/configure-warp/route-traffic/split-tunnels/#change-split-tunnels-mode) is set to **Exclude** or **Include** mode.

2. If you are using **Include** mode, add your SSH server's IP/CIDR range to the list. Your list should also include the [domains necessary for Cloudflare Zero Trust functionality](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/route-traffic/split-tunnels/#cloudflare-zero-trust-domains).

3. If you are using **Exclude** mode:

   a. Delete your SSH server's IP/CIDR range from the list. 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 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:

   In **Base CIDR**, enter the RFC 1918 range that you deleted from Split Tunnels. In **Excluded CIDRs**, enter the IP/CIDR range used by your SSH server. 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/connections/connect-devices/warp/configure-warp/warp-settings/#allow-users-to-enable-local-network-exclusion).

## 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 resource. We recommend using the server hostname, for example `production-server`. The hostname does not need to be unique and can be reused for multiple targets. Hostnames are used to define the subset of targets included in an infrastructure application and are not used in DNS address resolution.
  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/connections/connect-networks/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

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

     | Type    | Item       | Permission |
     | ------- | ---------- | ---------- |
     | Account | Zero Trust | Edit       |

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

     ```sh
     curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/infrastructure/targets \
     --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
     --data '{
       "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 will represent the application. The application definition will apply to all targets that share the selected hostname, including any targets added in the future.

  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/policies/access/) 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

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

     | Type    | Item                    | Permission |
     | ------- | ----------------------- | ---------- |
     | Account | Access: Apps & Policies | Edit       |

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

     ```sh
     curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/apps \
     --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
     --header "Content-Type: application/json" \
     --data '{
       "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.

Note

Gateway [network policies](https://developers.cloudflare.com/cloudflare-one/policies/gateway/network-policies/) take precedence over infrastructure policies. For example, if you block port `22` for all users in Gateway, then no one can SSH over port `22` to your targets.

## 6. 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:

   ```bash
   curl --request POST \
   "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/gateway_ca" \
   --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:

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

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

The following procedure makes two changes to the `sshd_config` file on the remote target machine. The first change requires that you uncomment a field already set in most default configurations; the second change adds a new field.

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

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

2. Go to the row named `PubkeyAuthentication`. In most default configurations, the row will appear commented out as follows:

   ```txt
   # PubkeyAuthentication yes
   ```

3. Remove the `#` symbol to uncomment the line:

   ```txt
   PubkeyAuthentication yes
   ```

4. Add a new line below `PubkeyAuthentication`:

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

5. Save the file and quit the editor. You might need to use the following command again to save and exit.

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

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

### Restart your SSH server

Once you have modified your SSHD configuration, restart the SSH service on the remote machine.

* Debian/Ubuntu

  For older Debian/Ubuntu versions:

  ```sh
  sudo service ssh restart
  ```

  For newer Debian/Ubuntu versions:

  ```sh
  sudo systemctl restart ssh
  ```

* CentOS/RHEL

  For CentOS/RHEL 6 and older:

  ```sh
  sudo service sshd restart
  ```

  For CentOS/RHEL 7 and newer:

  ```sh
  sudo systemctl restart sshd
  ```

## 7. 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/connections/connect-networks/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>
```

SSH with Access for Infrastructure also supports `scp` and `rsync` commands. At this time, `sftp` is not supported. Note that `scp` commands that utilize OpenSSH versions > 9.0 will not work at this time because OpenSSH 9.0+ leverages `sftp` to execute the command.

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. These logs are encrypted using a public key provided by the customer and are not visible to Cloudflare.

### 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/policies/gateway/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/):

  ```sh
  curl --request PUT https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/audit_ssh_settings \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --data '{
    "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. Select a user who was allowed to access the target.

3. Select **Download** to download the session's command log.

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

Enterprise customers can also export command logs using [Logpush](https://developers.cloudflare.com/cloudflare-one/insights/logs/logpush/).
