Bash History Security: 5 Ways to Keep Passwords and API Keys Out of Plain Text
You've Done It: Pasted a Password, Hit Enter, Immediately Panicked
You paste a curl command with an API key embedded. You hit Enter. The command works. Then you realize: where did that string just land? Bash saved it. To ~/.bash_history. Which is readable by anyone with access to your account.
If you're on a shared server with other admins, that history file is the first place people look when they're trying to debug what you did. If that history file contains a database password, an AWS access key, or a service account token, you've created a security incident waiting to happen.
Bash gives you precise control over what gets saved and how to remove it after the fact. Most infrastructure engineers don't use these controls, which is why credentials leak into history files more often than they should.
How Bash History Actually Works (This Matters)
Understanding the mechanics makes everything else click.
Bash keeps history in two places:
- In memory during your session (stored in RAM)
- On disk when the session ends (written to
~/.bash_history)
Check your current settings:
echo $HISTFILE # Where history gets written
echo $HISTSIZE # How many commands stay in memory (default: 1000)
echo $HISTFILESIZE # How many lines the disk file keeps (default: 2000)
echo $HISTCONTROL # What Bash ignores when saving
/home/username/.bash_history
1000
2000
ignoredups:ignorespaceKey insight: During your session, the command only exists in memory. It hits disk when you close the terminal cleanly. If your terminal crashes or you kill the session, that command may never reach disk.
But that's not reliable for security. Assume everything you type will be saved unless you take explicit action.
Method 1: The Leading Space Trick (Simplest)
Add a space before the command, and Bash won't save it. Period.
Without protection:
export API_KEY="secret123" # SAVED to history
With protection:
export API_KEY="secret123" # NOT saved (note the leading space)
Requirement: Your HISTCONTROL must include ignorespace. Check:
echo $HISTCONTROL
If output shows ignorespace or ignoreboth, you're good. If empty or missing, add it to ~/.bashrc:
echo 'export HISTCONTROL=ignorespace' >> ~/.bashrc
source ~/.bashrc
⚠️ Gotcha: You have to remember to add the space. One slip-up and the credential is saved. This is a habit, not a bulletproof system.
Method 2: Delete After the Fact (Post-Execution)
Realized you already ran a command with sensitive data? Use history -d to remove it.
List your history:
history | tail -10
497 sudo systemctl restart nginx
498 export DB_PASS="hunter2"
499 curl https://api.example.com/token
500 ls -la /etc/nginxDelete line 498:
history -d 498
The entry is gone from memory. But it's still on disk (in ~/.bash_history) until you flush the in-memory list:
history -w
This overwrites ~/.bash_history with the current in-memory list (minus the deleted entry).
⚠️ Important: If you close the terminal without running history -w, the deletion only lives in memory. When the session ends, Bash appends the in-memory list to the disk file, which doesn't include the deleted entry. But don't rely on this—run history -w immediately.
Method 3: Ignore Patterns Globally (HISTIGNORE)
Tell Bash to never save commands matching certain patterns. Add to ~/.bashrc:
export HISTIGNORE="ls:ls -la:cd:pwd:clear:history:exit:export *:curl *token*:mysql -u*"
Patterns are colon-separated. Wildcards work:
export *ignores all export commandscurl *token*ignores any curl with "token" in itmysql -u*ignores mysql commands with -u flag
Practical HISTIGNORE for sysadmins:
export HISTIGNORE="ls:cd:pwd:clear:history:exit:\
curl *password*:curl *token*:curl *auth*:\
mysql -u*:psql -U*:export *KEY*:export *TOKEN*:export *PASS*:\
ssh-keygen*:ssh *-p*"
Add to ~/.bashrc and source:
source ~/.bashrc
⚠️ Warning: Patterns are greedy. If you ignore sudo *, you lose the audit trail for every privileged command. Be specific. Also note that patterns don't retroactively delete history—only new commands matching the pattern are ignored.
Method 4: Global Settings (HISTCONTROL)
Set HISTCONTROL to control blanket behaviors:
# Ignore duplicates (repeated commands)
export HISTCONTROL=ignoredups
# Ignore leading spaces AND duplicates (recommended)
export HISTCONTROL=ignoreboth
Add to ~/.bashrc:
echo 'export HISTCONTROL=ignoreboth' >> ~/.bashrc
source ~/.bashrc
What this does:
ignoredups: If you run the same command twice in a row, only the first is savedignorespace: Commands with a leading space aren't savedignoreboth: Both behaviors combined
⚠️ Note: This is a global setting. It applies to all commands. Use HISTIGNORE if you want pattern-based filtering instead.
Method 5: Disable History Entirely (Session-Level)
If you're doing sensitive work (credential setup, incident response, security audits on a shared box), disable history for that session:
export HISTFILE=/dev/null
From that point forward:
- The in-memory list still builds (you can use the up arrow to navigate)
- Nothing gets written to disk
- When the session ends, the in-memory list evaporates
Use this when:
- Setting up service accounts or credentials
- Auditing configurations you don't want logged
- Incident response on shared infrastructure
To re-enable:
export HISTFILE=~/.bash_history
⚠️ Compliance note: Some organizations require audit trails for all privileged commands. Disabling history may violate compliance policy. Know your environment before doing this.
Clear Entire History (Nuclear Option)
To start fresh and delete all past commands:
history -c && history -w
What this does:
history -c: Clear the in-memory listhistory -w: Write the empty list to~/.bash_history
Verify it worked:
cat ~/.bash_history
# Should return nothing (or very few lines)
⚠️ This is destructive. You're permanently deleting your history. Only do this if you're certain you don't need it for audit purposes.
Real-World Gotchas and Compliance Issues
Gotcha 1: Multiple Shells
If you use both bash and zsh, history settings are separate. A command typed in zsh won't respect HISTCONTROL. Check your shell:
echo $SHELL
If you're using multiple shells, add history controls to each shell's config:
- Bash:
~/.bashrc - Zsh:
~/.zshrc
Gotcha 2: Shared Servers and Audit Requirements
If you're on a shared server (especially in an enterprise), disabling or deleting history may trigger alerts or violate compliance. Your infrastructure team may be monitoring for history -c or HISTFILE=/dev/null.
Before using these techniques on shared systems, check with your security/infrastructure team.
Gotcha 3: Sudo Commands
Commands run with sudo are often logged separately in /var/log/auth.log or systemd journal. Removing them from bash history doesn't remove the audit trail. Conversely, sudo commands may require audit logging for compliance.
Verify:
sudo journalctl -u sudo | tail -10
Gotcha 4: Shell History Merging
If you have multiple terminals open, they might overwrite each other's history. The last one to close wins. Running history -w from one terminal might get overwritten when another terminal closes.
If you rely on this, disable history merging:
# Prevent history merging (append instead)
shopt -s histappend
# In ~/.bashrc:
echo 'shopt -s histappend' >> ~/.bashrc
The Practical Setup: What to Actually Do
Add this to your ~/.bashrc:
# Ignore duplicates and leading spaces
export HISTCONTROL=ignoreboth
# Ignore specific patterns
export HISTIGNORE="ls:cd:pwd:clear:history:exit:\
curl *password*:curl *token*:\
export *KEY*:export *TOKEN*:export *PASS*"
# Keep more history (optional)
export HISTSIZE=5000
export HISTFILESIZE=10000
# Append history instead of overwriting
shopt -s histappend
Then source it:
source ~/.bashrc
This covers 80% of use cases without being overly restrictive.
When Credentials DO Leak: What to Do
If you discover credentials in someone's bash history (or your own):
- Immediately rotate the credential (password, API key, token, etc.)
- Audit usage: Check logs to see if the credential was used by unauthorized parties
- Delete the history file:
history -c && history -w - Report it (internal security team, incident response, etc.)
- Learn the lesson: Add the pattern to
HISTIGNOREor start using the leading-space trick
For production systems, consider integrating bash history into your SIEM (Security Information and Event Management) system so credentials in history trigger alerts automatically.
References
- Bash manual: History interaction
- Linux Foundation: Securing User Accounts
- systemd journal documentation (for sudo audit logs)
- OWASP: Secrets management best practices