nohup COMMAND [ARG]...
nohup OPTION
The nohup command (no hangup) runs the specified command immune to hangup signals,
with output redirected to a file. This allows processes to continue running even after you log out
or close your terminal session. By default, both standard output and standard error are redirected
to nohup.out in the current directory (or in the user's home directory if the current
directory is not writable).
This is essential for running long-duration processes on remote servers where network interruptions
or accidental disconnections would otherwise terminate the process. Unlike background jobs started
with & alone, nohup-protected processes survive terminal closure because they ignore
the SIGHUP (hangup) signal that the shell sends to its child processes upon termination.
# Run a script that will continue after logout
nohup ./long_running_backup.sh &
# The output shows the job number and PID
# [1] 12345
[1] 12345
nohup: ignoring input and appending output to 'nohup.out'
The process ID (12345) is displayed, allowing you to monitor or kill the process later. All output will be written to nohup.out in the current directory. You can now safely log out and the backup will continue.
# Redirect output to a specific file
nohup python data_analysis.py > analysis.log 2>&1 &
# Check the job status
jobs -l
# Monitor the output in real-time
tail -f analysis.log
[1] 23456
nohup: ignoring input and redirecting stderr to stdout
# jobs -l output:
[1]+ 23456 Running nohup python data_analysis.py > analysis.log 2>&1 &
# tail -f shows live output from the script
The syntax 2>&1 redirects standard error (file descriptor 2) to standard output (file descriptor 1), so both streams go to the same file. This is crucial for capturing error messages.
# Run without creating any output file
nohup /usr/local/bin/database_maintenance.sh > /dev/null 2>&1 &
# Store the PID for later reference
echo $! > /var/run/db_maintenance.pid
# Check if process is running
ps -p $(cat /var/run/db_maintenance.pid)
[1] 34567
# PID stored in file for tracking
# ps output shows process is running:
PID TTY TIME CMD
34567 ? 00:00:12 database_mainte
The special variable $! contains the PID of the last background process. Storing it in a file allows system monitoring tools or other scripts to track the process.
# Use a subshell to run multiple commands
nohup bash -c 'echo "Starting process";
./step1_download.sh;
./step2_process.sh;
./step3_upload.sh;
echo "Process complete"' > pipeline.log 2>&1 &
# Alternative: Use a wrapper script
cat > run_pipeline.sh << 'EOF'
#!/bin/bash
echo "Pipeline started at $(date)"
./step1_download.sh
./step2_process.sh
./step3_upload.sh
echo "Pipeline completed at $(date)"
EOF
chmod +x run_pipeline.sh
nohup ./run_pipeline.sh > pipeline.log 2>&1 &
[1] 45678
# In pipeline.log:
Starting process
Pipeline started at Sun Dec 14 12:30:45 EST 2025
[output from step1...]
[output from step2...]
[output from step3...]
Pipeline completed at Sun Dec 14 15:22:11 EST 2025
Process complete
Using bash -c allows you to run multiple commands, but creating a separate script is often cleaner for complex pipelines. Both approaches work with nohup.
# Start a long-running process
nohup ./video_encoder.sh input.mp4 output.mp4 > encoding.log 2>&1 &
PID=$!
echo "Started encoding with PID: $PID"
# Check if process is still running
if ps -p $PID > /dev/null; then
echo "Process is running"
else
echo "Process has finished or died"
fi
# View the last 20 lines of output
tail -20 encoding.log
# Follow the output in real-time (Ctrl+C to stop viewing)
tail -f encoding.log
# Check process resource usage
ps -p $PID -o pid,pcpu,pmem,etime,cmd
# Get more detailed information
top -p $PID -n 1
Started encoding with PID: 56789
Process is running
# tail -20 shows recent output lines
# tail -f shows live streaming output
# ps output:
PID %CPU %MEM ELAPSED CMD
56789 95.3 2.1 00:15:42 ./video_encoder.sh input.mp4 output.mp4
The tail -f command is invaluable for monitoring nohup processes. Press Ctrl+C to stop viewing without killing the background process. The ELAPSED column in ps shows how long the process has been running.
# Run Java application with specific memory settings
nohup java -Xmx2048m -Xms1024m \
-jar /opt/myapp/application.jar \
--spring.profiles.active=production \
> /var/log/myapp/app.log 2>&1 &
# Save the PID
echo $! > /var/run/myapp.pid
# Create a status check script
cat > check_app.sh << 'EOF'
#!/bin/bash
PID_FILE="/var/run/myapp.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null 2>&1; then
echo "Application is running (PID: $PID)"
# Check if listening on expected port
if netstat -tuln | grep -q ":8080"; then
echo "Port 8080 is active"
else
echo "WARNING: Port 8080 not active"
fi
else
echo "Application is NOT running"
fi
else
echo "PID file not found"
fi
EOF
chmod +x check_app.sh
./check_app.sh
[1] 67890
# check_app.sh output:
Application is running (PID: 67890)
Port 8080 is active
# In /var/log/myapp/app.log:
2025-12-14 12:45:23.456 INFO Starting ApplicationMain
2025-12-14 12:45:25.123 INFO Tomcat started on port(s): 8080 (http)
2025-12-14 12:45:25.234 INFO Started ApplicationMain in 2.1 seconds
For production Java applications, consider using systemd service files or dedicated process managers like supervisord instead of nohup. They provide better control, automatic restarts, and easier management.
# Add to crontab (crontab -e)
# Run nightly backup at 2 AM
0 2 * * * /usr/bin/nohup /opt/scripts/nightly_backup.sh > /var/log/backups/backup_$(date +\%Y\%m\%d).log 2>&1
# More complex cron with email notification
0 3 * * * /usr/bin/nohup /opt/scripts/data_sync.sh > /tmp/sync_$(date +\%Y\%m\%d_\%H\%M).log 2>&1 && echo "Sync completed" | mail -s "Data Sync Success" admin@example.com || echo "Sync failed - check logs" | mail -s "Data Sync FAILURE" admin@example.com
# Create a cron wrapper script for better control
cat > /opt/scripts/cron_wrapper.sh << 'EOF'
#!/bin/bash
SCRIPT="$1"
LOG_DIR="/var/log/cron_jobs"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
SCRIPT_NAME=$(basename "$SCRIPT" .sh)
LOG_FILE="$LOG_DIR/${SCRIPT_NAME}_${TIMESTAMP}.log"
mkdir -p "$LOG_DIR"
echo "Starting $SCRIPT at $(date)" > "$LOG_FILE"
nohup "$SCRIPT" >> "$LOG_FILE" 2>&1
EXIT_CODE=$?
echo "Finished $SCRIPT at $(date) with exit code $EXIT_CODE" >> "$LOG_FILE"
# Rotate old logs (keep last 30 days)
find "$LOG_DIR" -name "${SCRIPT_NAME}_*.log" -mtime +30 -delete
exit $EXIT_CODE
EOF
chmod +x /opt/scripts/cron_wrapper.sh
# Use in crontab
0 4 * * * /opt/scripts/cron_wrapper.sh /opt/scripts/important_task.sh
# In /var/log/cron_jobs/important_task_20251214_040000.log:
Starting /opt/scripts/important_task.sh at Sun Dec 14 04:00:00 EST 2025
[task output here...]
Finished /opt/scripts/important_task.sh at Sun Dec 14 04:23:45 EST 2025 with exit code 0
While nohup in cron is often redundant (cron jobs already don't receive SIGHUP), it's useful for output redirection and when scripts themselves spawn long-running child processes.
# Create input file
cat > input_data.txt << EOF
command1
command2
command3
EOF
# Run program with input from file
nohup ./batch_processor < input_data.txt > results.log 2>&1 &
# Alternative: Use here-document for small inputs
nohup ./config_updater > update.log 2>&1 << EOF &
option1=value1
option2=value2
option3=value3
EOF
# For interactive programs that need input, use expect or similar
cat > auto_install.exp << 'EOF'
#!/usr/bin/expect -f
set timeout -1
spawn /usr/local/bin/installer.sh
expect "Enter installation directory:"
send "/opt/myapp\r"
expect "Proceed with installation? (y/n)"
send "y\r"
expect eof
EOF
chmod +x auto_install.exp
nohup ./auto_install.exp > install.log 2>&1 &
[1] 78901
# In results.log:
Processing command1...
Processing command2...
Processing command3...
Batch processing complete
# For expect script:
Installation directory set to /opt/myapp
Proceeding with installation...
Installation completed successfully
nohup automatically redirects stdin from /dev/null, so any program requiring interactive input will fail. Use input redirection from files, or tools like expect for interactive programs.
# Start a process and save PID
nohup ./long_service.sh > service.log 2>&1 &
SERVICE_PID=$!
echo $SERVICE_PID > service.pid
echo "Service started with PID: $SERVICE_PID"
# Later, to gracefully stop (allows cleanup):
# Send SIGTERM (default kill signal)
kill $(cat service.pid)
# Wait a few seconds for graceful shutdown
sleep 5
# Check if it's still running
if ps -p $(cat service.pid) > /dev/null 2>&1; then
echo "Process still running, forcing termination"
# Send SIGKILL (force kill)
kill -9 $(cat service.pid)
else
echo "Process terminated gracefully"
fi
# Clean up PID file
rm -f service.pid
# More sophisticated stop script
cat > stop_service.sh << 'EOF'
#!/bin/bash
PID_FILE="service.pid"
TIMEOUT=30
if [ ! -f "$PID_FILE" ]; then
echo "PID file not found"
exit 1
fi
PID=$(cat "$PID_FILE")
if ! ps -p $PID > /dev/null 2>&1; then
echo "Process not running"
rm -f "$PID_FILE"
exit 0
fi
echo "Sending SIGTERM to PID $PID"
kill $PID
# Wait for graceful shutdown
ELAPSED=0
while ps -p $PID > /dev/null 2>&1; do
sleep 1
ELAPSED=$((ELAPSED + 1))
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timeout reached, forcing termination"
kill -9 $PID
break
fi
if [ $((ELAPSED % 5)) -eq 0 ]; then
echo "Waiting for shutdown... ($ELAPSED seconds)"
fi
done
echo "Process terminated"
rm -f "$PID_FILE"
EOF
chmod +x stop_service.sh
Service started with PID: 89012
# When stopping:
Sending SIGTERM to PID 89012
Waiting for shutdown... (5 seconds)
Waiting for shutdown... (10 seconds)
Process terminated
Always try SIGTERM (kill) before SIGKILL (kill -9). SIGTERM allows the process to clean up resources, close files properly, and exit gracefully. SIGKILL terminates immediately without cleanup.
#!/bin/bash
# File: service_manager.sh
# Manages a nohup-based service with proper logging and control
SERVICE_NAME="data_processor"
SERVICE_CMD="/opt/apps/data_processor/run.sh"
PID_FILE="/var/run/${SERVICE_NAME}.pid"
LOG_DIR="/var/log/${SERVICE_NAME}"
LOG_FILE="${LOG_DIR}/${SERVICE_NAME}_$(date +%Y%m%d).log"
# Ensure log directory exists
mkdir -p "$LOG_DIR"
start_service() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null 2>&1; then
echo "Service already running with PID $PID"
return 1
else
echo "Removing stale PID file"
rm -f "$PID_FILE"
fi
fi
echo "Starting $SERVICE_NAME..."
echo "$(date): Starting service" >> "$LOG_FILE"
nohup $SERVICE_CMD >> "$LOG_FILE" 2>&1 &
PID=$!
echo $PID > "$PID_FILE"
# Verify it started successfully
sleep 2
if ps -p $PID > /dev/null 2>&1; then
echo "Service started successfully with PID $PID"
echo "Log file: $LOG_FILE"
return 0
else
echo "Service failed to start - check $LOG_FILE"
rm -f "$PID_FILE"
return 1
fi
}
stop_service() {
if [ ! -f "$PID_FILE" ]; then
echo "Service not running (no PID file)"
return 0
fi
PID=$(cat "$PID_FILE")
if ! ps -p $PID > /dev/null 2>&1; then
echo "Service not running (stale PID file)"
rm -f "$PID_FILE"
return 0
fi
echo "Stopping $SERVICE_NAME (PID: $PID)..."
echo "$(date): Stopping service" >> "$LOG_FILE"
kill $PID
# Wait up to 30 seconds for graceful shutdown
TIMEOUT=30
ELAPSED=0
while ps -p $PID > /dev/null 2>&1; do
sleep 1
ELAPSED=$((ELAPSED + 1))
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timeout - forcing termination"
kill -9 $PID
break
fi
done
rm -f "$PID_FILE"
echo "Service stopped"
echo "$(date): Service stopped" >> "$LOG_FILE"
return 0
}
status_service() {
if [ ! -f "$PID_FILE" ]; then
echo "$SERVICE_NAME is NOT running"
return 1
fi
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null 2>&1; then
echo "$SERVICE_NAME is running (PID: $PID)"
echo "Uptime: $(ps -p $PID -o etime= | tr -d ' ')"
echo "Memory: $(ps -p $PID -o rss= | awk '{printf "%.1f MB", $1/1024}')"
echo "CPU: $(ps -p $PID -o %cpu=)%"
echo "Log file: $LOG_FILE"
# Show last 5 log lines
echo -e "\nRecent log entries:"
tail -5 "$LOG_FILE"
return 0
else
echo "$SERVICE_NAME is NOT running (stale PID file)"
rm -f "$PID_FILE"
return 1
fi
}
restart_service() {
echo "Restarting $SERVICE_NAME..."
stop_service
sleep 2
start_service
}
tail_logs() {
if [ -f "$LOG_FILE" ]; then
echo "Following log file (Ctrl+C to stop):"
tail -f "$LOG_FILE"
else
echo "Log file not found: $LOG_FILE"
return 1
fi
}
# Rotate logs older than 7 days
rotate_logs() {
echo "Rotating old log files..."
find "$LOG_DIR" -name "${SERVICE_NAME}_*.log" -mtime +7 -delete
echo "Log rotation complete"
}
# Main command dispatcher
case "$1" in
start)
start_service
;;
stop)
stop_service
;;
restart)
restart_service
;;
status)
status_service
;;
logs)
tail_logs
;;
rotate)
rotate_logs
;;
*)
echo "Usage: $0 {start|stop|restart|status|logs|rotate}"
exit 1
;;
esac
exit $?
# Start the service
$ ./service_manager.sh start
Starting data_processor...
Service started successfully with PID 12345
Log file: /var/log/data_processor/data_processor_20251214.log
# Check status
$ ./service_manager.sh status
data_processor is running (PID: 12345)
Uptime: 00:15:23
Memory: 145.2 MB
CPU: 12.3%
Log file: /var/log/data_processor/data_processor_20251214.log
Recent log entries:
2025-12-14 13:00:01 Processing batch 1234
2025-12-14 13:00:15 Processing batch 1235
2025-12-14 13:00:29 Processing batch 1236
# Stop the service
$ ./service_manager.sh stop
Stopping data_processor (PID: 12345)...
Service stopped
This production-ready script demonstrates best practices: PID file management, graceful shutdown with timeout, status reporting, log rotation, and proper error handling. Use this as a template for managing nohup-based services.
nohup command & together. Using nohup without & means the
command runs in the foreground and you can't use your terminal. The & backgrounds the process
and returns control immediately.
nohup command > file.log 2>&1 & to capture both standard output and error
messages in the same file. The 2>&1 syntax is crucial for debugging as it
captures error messages that would otherwise be lost.
nohup command &; echo $! > command.pid. This allows
you to monitor, manage, or terminate the process later. Without the PID, finding and
controlling your process becomes much harder.
systemd, supervisord, or
screen/tmux instead of nohup. These provide better process management, automatic
restarts, logging integration, and easier monitoring. nohup is great for quick tasks but not
ideal for complex service management.
logrotate or similar tools for automatic log management.
ps, review the first few lines of output, and ensure the process is
actually doing what you expect. It's frustrating to log out only to discover the process
failed immediately.
disown -h %jobnum to
remove it from the shell's job table and make it immune to SIGHUP. However, you'll need to
manually handle output redirection. bg can move a stopped job to background
before using disown.