How to restrict an SSH key to one command or address

Put options in front of the key in authorized_keys: restrict,command="/usr/local/bin/backup",from="203.0.113.10" ssh-ed25519 AAAA.... With that line, the key always runs that one command, whatever the client asks for, only from that address, with no terminal and no forwarding. The limits apply to that key only, not to other keys or passwords for the account.

Updated . All options, the wrapper script, rrsync and SFTP run with OpenSSH 10.0 on Debian 13, in a throwaway container.

The options

OptionWhat it doesWhat the other side sees
restrictNo forwarding of any kind, no terminal, no ~/.ssh/rcPTY allocation request failed on channel 0
command="..."Always runs this commandThe output of that command
from="..."Only from these addresses or namesPermission denied
permitopen="host:port"Limits ssh -L and -W to this destinationstdio forwarding failed or open failed
permitlisten="[host:]port"Limits ssh -R to this portremote port forwarding failed

Start with restrict and add back what the key needs. It also covers any restriction added to OpenSSH in the future, which a list of no-pty,no-port-forwarding and so on does not. Options go on one line, separated by commas, with no spaces between them.

One command only

restrict,command="/usr/local/bin/backup" ssh-ed25519 AAAA... backup-job

Now ssh user@server and ssh user@server rm -rf / both run the backup and nothing else.

A few allowed commands, with SSH_ORIGINAL_COMMAND

What the client asked for is in the variable SSH_ORIGINAL_COMMAND, and a small script can check it:

#!/bin/sh
# /usr/local/bin/ssh-allow
case "$SSH_ORIGINAL_COMMAND" in
  "uptime"|"df -h")
    exec $SSH_ORIGINAL_COMMAND
    ;;
  *)
    echo "Not allowed: $SSH_ORIGINAL_COMMAND" >&2
    exit 1
    ;;
esac
restrict,command="/usr/local/bin/ssh-allow" ssh-ed25519 AAAA... monitoring

In our test, ssh user@server uptime worked, and ssh user@server "uptime; id" answered Not allowed: uptime; id. Match whole strings as above. A pattern like uptime* lets through anything that starts with the word, and never pass the variable to sh -c or eval.

Choose the allowed commands with care too. A command that can write files or start a shell, such as an editor, undoes the restriction.

Limit by address

from="203.0.113.10,198.51.100.0/24" ssh-ed25519 AAAA... office

Patterns and CIDR ranges both work, and ! excludes an address. Use IP addresses rather than host names, since a host name depends on reverse DNS. When the address does not match, the client sees Permission denied and the server log explains:

Authentication tried for user with correct key but not from a permitted host (host=198.51.100.7, ip=198.51.100.7, required=203.0.113.10).

A key only for one tunnel

restrict,port-forwarding,permitopen="10.0.1.10:5432",command="/usr/sbin/nologin" ssh-ed25519 AAAA... db-tunnel

This key can reach the database at that address and port. A forward to another port fails, and a command only gets This account is currently not available. To use it, add -N so that ssh asks for no command:

ssh -N -L 5432:10.0.1.10:5432 user@server

A hostname in permitopen must match exactly what the client sends, with no lookup. localhost and 127.0.0.1 count as different.

Watch out: this still allows ssh -R

port-forwarding turns on both directions, and permitopen limits only one. In our test, the key above could still open a remote forward with ssh -R. permitlisten="none" is not a way out, because the line is then invalid and the key stops working altogether. Do one of these instead:

  • Set permitlisten to a single port that nothing uses, for example permitlisten="127.0.0.1:9". We tested that a forward on any other port is refused.
  • Limit the account in the server config, which applies to all its keys:
# /etc/ssh/sshd_config.d/10-tunnel.conf
Match User tunnel
    AllowTcpForwarding local

For what -L, -R and -D do, see local, remote and dynamic SSH tunnels.

File transfer keys

restrict alone does not stop file transfer. In our test, sftp still worked with it, because SFTP needs no terminal or forwarding. To allow SFTP and nothing else:

restrict,command="internal-sftp" ssh-ed25519 AAAA... files

To allow rsync in one folder, use rrsync, which comes with rsync. On Debian it is at /usr/bin/rrsync, and other systems can differ. With -ro it allows reading only, and we confirmed that uploads then fail:

restrict,command="/usr/bin/rrsync -ro /srv/backup" ssh-ed25519 AAAA... backup-read

What these options do not do

  • They bind only that key. Another key, or a password, on the same account has none of these limits. Give each job its own account where you can, and turn off passwords.
  • The user can edit the file. If the account can write to its own authorized_keys, a command that writes files can remove the limits. For a firm boundary, keep the file owned by root. See authorized_keys explained.
  • Line order matters. If the same key also appears on an unrestricted line above, that line wins.

Questions

Does command= in authorized_keys block SFTP?

Yes, when command= points to anything other than internal-sftp, because SFTP then runs that command instead. restrict alone does not block SFTP.

What is SSH_ORIGINAL_COMMAND?

An environment variable that holds the command the client asked for when a forced command runs. A wrapper script can check it and allow only a few exact commands.

Can I use a host name in from=?

Yes, but it depends on reverse DNS, which the other side may control. IP addresses and CIDR ranges are safer.

Sources

More in Securing a server

All guides