Your shell already saves them
Every command you type is kept in the shell history, but the defaults are small and the file is written only when the shell exits. Two sessions at once can overwrite each other's lines. For bash, add this to ~/.bashrc:
HISTSIZE=100000
HISTFILESIZE=100000
HISTCONTROL=ignoreboth
HISTTIMEFORMAT='%F %T '
shopt -s histappend
PROMPT_COMMAND='history -a'
HISTSIZEandHISTFILESIZEkeep up to 100,000 lines in memory and in the file.ignorebothskips repeated lines and lines that start with a space.HISTTIMEFORMATadds the date and time tohistory.histappendandhistory -aadd each command to the file right away, so a lost connection loses nothing. If yourPROMPT_COMMANDalready has a value, addhistory -a;in front of it.
For zsh, the default on macOS, put this in ~/.zshrc:
HISTFILE=~/.zsh_history
HISTSIZE=100000
SAVEHIST=100000
setopt EXTENDED_HISTORY INC_APPEND_HISTORY HIST_IGNORE_SPACE HIST_IGNORE_DUPS
Do the same on the servers you use most. Over SSH, the history is saved on the server, for the user you logged in as.
Find a command again
Press Ctrl-R and type any part of the command. Press Ctrl-R again for older matches, Return to run, or an arrow key to edit first. To see a list instead:
history | grep journalctl
With the timestamps set above, the list shows when each command ran:
1 2026-11-06 15:21:40 echo one
2 2026-11-06 15:21:40 diskfree /tmp
3 2026-11-06 15:21:40 uptime -p # @health
The last line shows a useful trick. A comment after # does nothing when the command runs, but it stays in the history. Add a tag such as # @health or # @deploy, and Ctrl-R then @health finds every command you tagged that way.
Aliases and functions for the commands you repeat
A command you type every week deserves a short name. An alias is fixed text. A function can take arguments:
alias logs='journalctl -u nginx --since "1 hour ago" --no-pager'
diskfree() { df -h "${1:-/}" | tail -n 1; }
diskfree alone checks /, and diskfree /var checks that folder. Check what a name does with type logs. Aliases work only in interactive shells, so a script or ssh server logs will not find them. Use a function in a sourced file, or a small script in ~/bin, for those cases.
A saved command per server in your SSH config
For a command you always run on one server, such as following a log, the SSH config can run it on login. Put this in ~/.ssh/config:
Host web1-logs
HostName web1
RemoteCommand tail -f /var/log/nginx/error.log
RequestTTY yes
ssh web1-logs then opens the log directly. RequestTTY yes gives the command a terminal, so Ctrl-C works. The entry cannot also take a command on the command line: ssh web1-logs uptime fails with Cannot execute command-line and remote command. Override it when you need to:
ssh -o RemoteCommand=none web1-logs uptime
More patterns are in the ssh config file guide.
Keep them in one file you can carry
Put your aliases and functions in their own file, load it from your shell configuration, and copy it to the servers you use:
mkdir -p ~/.config/shell
$EDITOR ~/.config/shell/commands.sh
echo '[ -f ~/.config/shell/commands.sh ] && . ~/.config/shell/commands.sh' >> ~/.bashrc
ssh web1 'mkdir -p ~/.config/shell' && scp ~/.config/shell/commands.sh web1:.config/shell/
Add the same echo line to the server's ~/.bashrc once. Keeping the file in a git repository gives you its history and an easy way to bring it to a new machine. To send it to many servers at once, see how to run a command on multiple servers.
What never to save
History files are plain text. A password typed on the command line, such as mysql -pS3cret, stays there for anyone who can read the file. Three ways to keep secrets out:
- Start the line with a space. With
ignorebothin bash orHIST_IGNORE_SPACEin zsh, it is not saved. - Skip patterns for good with
HISTIGNOREin bash:
HISTIGNORE='*PASSWORD*:*TOKEN*'
- Remove a line you just typed. In bash 5 or later, this deletes the command before it:
history -d -2
That only fixes the history in memory. If the line already reached the history file, delete it there too, and change the password, because it may already have been copied elsewhere. Better still, let the program ask for the password, with mysql -p and nothing after it, or read secrets from a file with strict permissions.
Questions
Where does bash save my command history?
In ~/.bash_history, written when the shell exits unless you add history -a to PROMPT_COMMAND. zsh uses the file in HISTFILE, often ~/.zsh_history.
Does the history on a server include my local commands?
No. Each machine keeps its own history. Commands you type in an SSH session are saved on the server, in the history of the user you logged in as.
How do I stop a command from being saved?
Start it with a space when HISTCONTROL contains ignorespace in bash, or HIST_IGNORE_SPACE is set in zsh. HISTIGNORE can also skip patterns such as *TOKEN*.
What is the difference between an alias and a function?
An alias replaces the first word of a command with fixed text. A function is a small program that can take arguments, such as a folder or a service name.
Sources
- Bash manual: Bash History Facilities
- Bash manual: Bash Variables (HISTCONTROL, HISTIGNORE)
- zsh manual: History options
- ssh_config(5), RemoteCommand