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
    backup
  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  ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialOne backup@client1
         2  ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGenericKeyMaterialTwo deploy@ci

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

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

  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 backup-key.ed25519 backup@server.example.com
    Running scheduled backup via backup-run.sh...
    Backup completed successfully.
    Connection to server.example.com closed.
  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 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.

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

  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 backup-key.ed25519 -tt backup@server.example.com
    Running scheduled backup via backup-run.sh...
    Connection to server.example.com closed.
Discuss the article:

Comment anonymously. Login not required.