Skip to content
Cloudflare Docs

SSH with Access for Infrastructure (recommended)

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.

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. Create a Cloudflare Tunnel for your server by following our dashboard setup guide. You can skip the publish an application step and go straight to connecting a network.
  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 on your devices in Gateway with WARP mode.
  2. Enable the Gateway proxy for TCP.
  3. Create device enrollment rules 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, 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 so that the IP/CIDR of your SSH server routes through WARP.

  1. First, check whether your 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 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 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.

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:

  1. In Zero Trust, 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.
  1. In the dropdown menu, select the IP address and virtual network 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.

Next, create an Access application to secure the target.

5. Add an infrastructure application

  1. In Zero Trust, 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.
  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 and review the list of 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.

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

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:

    SelectorOperatorValueAction
    Access Infrastructure TargetisPresentAllow
  2. Update the policy's order of precedence using the dashboard or API.

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

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

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

  1. Create an API token with the following permissions:

    TypeItemPermission
    AccountAccess: SSH AuditingEdit
  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 is required:
  • Access: SSH Auditing Write
Add a new SSH Certificate Authority (CA)
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 is required:
  • Access: SSH Auditing Write
  • Access: SSH Auditing Read
List SSH Certificate Authorities (CA)
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:

    Terminal window
    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.

    Terminal window
    vim ca.pub
  3. In the ca.pub file, paste the public key without any modifications.

    ca.pub
    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:

    Terminal window
    :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.

    Terminal window
    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.

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.

For Debian/Ubuntu:

Terminal window
sudo systemctl reload ssh

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 before initiating the connection. Users do not need to modify any SSH configs on their device. For example, to SSH from a terminal:

Terminal window
ssh <username>@<target IP>

Access for Infrastructure also supports scp, sftp, and rsync commands. Refer to Known limitations for a list of unsupported SSH commands and features.

To learn more about user connections, refer to the Access for Infrastructure documentation.

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 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 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 the Cloudflare ssh-log-cli utility.

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

    Terminal window
    ./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 Zero Trust, 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:

  1. In Zero Trust, 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 policies.

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, go to Logs > Access.
  2. Filter the logs using the name of your SSH 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. In the following example, sshkey is the private key that matches the public key uploaded to Cloudflare.

    Terminal window
    ./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

Cloudflare allows you to send SSH command logs to storage destinations configured in Logpush, including third-party destinations. For a list of available data fields, refer to the SSH logs dataset.

To set up the Logpush job, refer to Logpush integration.

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.

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 allow the user to access the target machine.
  2. Check Cloudflare Tunnel health.
  3. Confirm user existence on the target server.
  4. Check your sshd_config file for misconfiguration.

1. Review Access policies

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

End users

As an end user, run warp-cli target list to verify that you have access to the target machine.

Terminal window
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 machine 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. 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.

  • If the target does not appear in the list, an administrator must audit the Access policies for the target machine 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.

  1. In Zero Trust, 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, the target SSH machine, or the sshd_config file.

  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 to review what criteria may be blocking the user.

By adding an Access policy to allow the user, the connection issue should be resolved. After saving your policy changes, attempt to connect to the target machine as the end user.

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

2. Check target machine connection

If the end user cannot connect to the target SSH machine, the tunnel you set up in step 1: Connect the server to Cloudflare may be down or inactive.

To check the status of your tunnel:

  1. In Zero Trust, 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. 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 says Active, and not Down, Degraded, or Inactive.

StatusMeaningRecommended Action
HealthyThe tunnel is active and serving traffic through four connections to the Cloudflare global network.No action is required. Your tunnel is running correctly.
InactiveThe 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 or step 4 in the Create a Tunnel API guide.
DownThe tunnel was previously connected but is currently disconnected because the cloudflared process has stopped.1. Ensure the cloudflared 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.
DegradedThe cloudflared connector is running and the tunnel is serving traffic, but at least one individual connection has failed. Further degradation in tunnel availability could risk the tunnel going down and failing to serve traffic.1. Review your cloudflared 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.

For detailed steps on troubleshooting, refer to the Troubleshooting Tunnel documentation. Review the Tunnel with Firewall documentation 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 target SSH server in the following step.

3. Confirm user existence on the target server

To verify the existence of the end user on the target SSH server, run the id <USERNAME> command on the target SSH 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 target machine, 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 SSH target machine.

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
# 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 to identify any conflicting directives.

  1. Back up the existing sshd_config file.

    Terminal window
    mv /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
  2. Create a new sshd_config file.

    Terminal window
    vi /etc/ssh/sshd_config
  3. Enter insert mode by pressing the i key on your keyboard.

  4. Paste in the example file.

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

  6. Enter :x to save and exit.

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

    For Debian/Ubuntu:

    Terminal window
    sudo systemctl reload ssh

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. The example sshd_config shared above enables debug logging and may expose more specific issues.