Restricting SSH keys to specific commands limits what automated jobs and external systems can do on a server, even if a key is stolen or misused. Per-key command restrictions in the authorized_keys file provide fine-grained control without needing separate Unix accounts for each integration.
The OpenSSH server reads /home//user/.ssh/authorized_keys (or the system-wide equivalent) and interprets each line as a public key plus optional restrictions. Options such as command=, no-port-forwarding, no-agent-forwarding, no-X11-forwarding, and no-pty are evaluated before the key is accepted and can enforce a single forced command whenever that key authenticates.
Per-key restrictions apply only to new SSH sessions and do not affect existing connections. Forced commands override whatever the SSH client requests, so a restricted key always runs the configured command and never receives an interactive shell. Careful sequencing of options and correct file permissions on /.ssh/authorized_keys and any called scripts is essential to avoid lockouts and unintended command execution.
Steps to restrict SSH commands per key:
- Open a terminal on the SSH server as the Unix account whose key must be restricted, or as a user with sudo access to that account’s home directory.
$ whoami backup
- Back up the existing authorized_keys file for that account.
$ cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak-$(date +%F)
Accidentally overwriting /.ssh/authorized_keys without a backup can remove all keys for the account and block key-based login until restored.
- Inspect the current keys so the correct line can be identified for restriction.
$ nl -ba ~/.ssh/authorized_keys 1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialOne backup@client1 2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialTwo deploy@cinl with -ba prints line numbers even for blank lines, which helps when editing specific entries.
- Open the authorized_keys file in a text editor.
$ nano ~/.ssh/authorized_keys
- Prefix the target key line with a forced command and recommended safety options, keeping all options before the key type and on a single line.
# Key restricted to a backup script and no interactive use command="/usr/local/bin/backup-run.sh",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialOne backup@client1 # Unrestricted key for interactive admin (unchanged) ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialTwo deploy@ci
All authorized_keys options, including command= and no-port-forwarding, must appear before the key type (for example, ssh-ed25519) and be separated by commas with no spaces.
- Ensure the forced command path exists on the server and is executable so the restricted key can run it successfully.
$ ls -l /usr/local/bin/backup-run.sh -rwxr-xr-x 1 root root 2048 Dec 1 10:15 /usr/local/bin/backup-run.sh
If the forced command binary or script does not exist or is not executable, logins using the restricted key will fail with a generic error and the backup or automation task will not run.
- Tighten permissions on the /.ssh directory and authorized_keys file for the account so SSH does not ignore them.
$ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys $ ls -ld ~/.ssh ~/.ssh/authorized_keys drwx------ 2 backup backup 4096 Dec 1 10:20 /home/backup/.ssh -rw------- 1 backup backup 812 Dec 1 10:20 /home/backup/.ssh/authorized_keys
OpenSSH refuses to use /.ssh/authorized_keys if it is writable by group or others, which can silently disable key-based access.
- From a separate system that holds the restricted private key, initiate an SSH connection without specifying a remote command to confirm the forced command runs automatically.
$ ssh -i backup-key.ed25519 backup@server.example.com Running scheduled backup via backup-run.sh... Backup completed successfully. Connection to server.example.com closed.
- Attempt to run an arbitrary command with the same key and verify that the configured forced command still runs instead of the requested command.
$ ssh -i backup-key.ed25519 backup@server.example.com "uname -a" Running scheduled backup via backup-run.sh... Backup completed successfully. Connection to server.example.com closed.
A key with command= set cannot execute arbitrary client-supplied commands; the SSH server always invokes the forced command, which ignores the remote command string.
- Use a small wrapper script when multiple safe commands must be allowed for a single key, relying on the SSH_ORIGINAL_COMMAND environment variable to decide what to run.
#!/bin/bash set -euo pipefail LOG_FILE="/var/log/ssh-allowed-commands.log" case "${SSH_ORIGINAL_COMMAND:-}" in "uptime") exec /usr/bin/uptime ;; "whoami") exec /usr/bin/whoami ;; "") echo "Interactive shells are not permitted for this key." >&2 exit 1 ;; *) echo "$(date -Is) denied: ${SSH_ORIGINAL_COMMAND} from ${SSH_CLIENT:-unknown}" >> "${LOG_FILE}" echo "Requested command is not permitted for this key." >&2 exit 1 ;; esacTo use a wrapper, set the per-key option to command=“/usr/local/bin/ssh-allowed-commands.sh” so the script receives any requested command via SSH_ORIGINAL_COMMAND and can enforce a whitelist.
- Restrict wrapper script permissions so only root can modify it, preventing unauthorized users from broadening what the key may execute.
$ chown root:root /usr/local/bin/ssh-allowed-commands.sh $ chmod 700 /usr/local/bin/ssh-allowed-commands.sh $ ls -l /usr/local/bin/ssh-allowed-commands.sh -rwx------ 1 root root 1024 Dec 1 10:25 /usr/local/bin/ssh-allowed-commands.sh
World-writable forced-command scripts allow any local user to change the behavior of restricted keys, defeating the purpose of per-key command locking.
- Verify that the restricted key cannot open an interactive shell by requesting one explicitly and confirming that only the forced command or wrapper logic runs.
$ ssh -i backup-key.ed25519 -tt backup@server.example.com Running scheduled backup via backup-run.sh... Connection to server.example.com closed.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
Comment anonymously. Login not required.
