Enforcing a forced command for SSH users limits what a remote account can do after authentication, which reduces attack surface and keeps automation behaviour predictable. Restricting a user to a single command or wrapper script is common for backup accounts, Git over SSH, and other service users that never need an interactive shell.
The OpenSSH server reads configuration from /etc/ssh/sshd_config and can override any requested shell or remote command using the ForceCommand directive. When ForceCommand appears inside a Match User or Match Group block, every connection that matches that account executes one configured program, no matter what the client requests on the command line.
Because ForceCommand removes normal shell access for the affected users, configuration mistakes can block maintenance tasks or confuse automation. Changes require root privileges and a restart of the ssh service on Ubuntu, so keeping a separate recovery path such as console access or an unmodified admin account is important before applying the setting to production systems.
$ whoami user
The administrative account needs sudo access to edit /etc/ssh/sshd_config and manage the ssh service.
$ id backupuser uid=1001(backupuser) gid=1001(backupuser) groups=1001(backupuser)
Using a dedicated account such as backupuser or git keeps forced-command behaviour separate from regular logins.
$ sudo nano /usr/local/sbin/ssh-forced-command.sh
Any preferred editor such as vim or micro works, as long as the script is saved under /usr/local/sbin or another root-owned directory in the PATH.
#!/usr/bin/env bash
logger -t ssh-forced-command "forced command for ${USER:-unknown} from ${SSH_CONNECTION:-unknown}"
echo "Forced command executed for ${USER:-unknown} on $(hostname)."
date
exit 0
The example script logs each connection via logger, prints a short status message, shows the current date, and exits cleanly.
$ sudo chmod 0755 /usr/local/sbin/ssh-forced-command.sh
Permissions 0755 allow all users to execute the script while keeping ownership and write access restricted to root.
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)
A valid backup of /etc/ssh/sshd_config is essential because a syntax error or incorrect Match block can prevent new SSH sessions from starting.
$ sudo nano /etc/ssh/sshd_config
The default location for the server configuration on Ubuntu is /etc/ssh/sshd_config; other Linux distributions usually use the same path.
Match User backupuser
ForceCommand /usr/local/sbin/ssh-forced-command.sh
PermitTTY no
X11Forwarding no
AllowTcpForwarding no
Options placed inside a Match block only apply to matching connections but also override subsequent global settings, so keeping this block at the end of the file reduces the chance of affecting other users.
Confirm that the Match User line uses the exact username and that /usr/local/sbin/ssh-forced-command.sh is spelled correctly; a wrong path causes every matching login to fail.
$ sudo sshd -t
No output from sshd -t indicates that the configuration syntax is valid; any reported line number should be corrected before continuing.
Related: How to test SSH server configuration
$ sudo systemctl restart ssh
On RHEL and similar distributions the unit name is typically sshd, so the restart command becomes sudo systemctl restart sshd.
$ sudo systemctl status ssh
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
Active: active (running) since Sun 2026-01-11 06:48:34 +08; 136ms ago
TriggeredBy: ● ssh.socket
Docs: man:sshd(8)
man:sshd_config(5)
Process: 13971 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
Main PID: 13972 (sshd)
Tasks: 1 (limit: 4546)
Memory: 2.7M (peak: 3.5M)
CPU: 31ms
CGroup: /system.slice/ssh.service
└─13972 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
The Active: active (running) line confirms that the daemon started successfully with the updated configuration.
$ ssh backupuser@host.example.net Forced command executed for backupuser on host. Sun Jan 11 06:48:34 AM +08 2026
The forced command setup is working when every connection for the matched user immediately runs the configured script and closes without providing an interactive shell or accepting arbitrary commands.