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:

  1. 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
    backupcmd
  2. 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.

  3. Inspect the current keys so the correct line can be identified for restriction.
    $ nl -ba ~/.ssh/authorized_keys
         1	command="/usr/local/bin/backup-run.sh",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPEPnTarmhV1ZKnuPQ3Bi9/k7+Fs0aJaGZFsAbmZ/P3B user@host
         2	ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMB5gIrE+VlSXny5KlaOW4NT8KbII3Kpc5uIheOiLU/t user@host

    nl with -ba prints line numbers even for blank lines, which helps when editing specific entries.

  4. Open the authorized_keys file in a text editor.
    $ nano ~/.ssh/authorized_keys
  5. 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.

  6. 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 125 Jan 11 06:53 /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.

  7. 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 backupcmd backupcmd 4096 Jan 11 06:53 /home/backupcmd/.ssh
    -rw------- 1 backupcmd backupcmd  285 Jan 11 06:53 /home/backupcmd/.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.

  8. 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 ~/.ssh/backupcmd-key.ed25519 backupcmd@host.example.net
    Running scheduled backup via backup-run.sh...
    Backup completed successfully.
  9. 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 ~/.ssh/backupcmd-key.ed25519 backupcmd@host.example.net "uname -a"
    Running scheduled backup via backup-run.sh...
    Backup completed successfully.

    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.

  10. 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
        ;;
    esac

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

  11. 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 420 Jan 11 06:53 /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.

  12. 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 ~/.ssh/backupcmd-key.ed25519 -tt backupcmd@host.example.net