Of course! Creating a daemon in Python is a common task for background services. A "daemon" is a long-running background process that typically runs detached from the terminal, surviving the shell that started it.

While you can write the low-level logic for detaching a process yourself, it's highly recommended to use a well-tested library. The most popular and robust library for this is python-daemon.
Here's a complete guide, starting with the best-practice approach using the library, followed by an explanation of the manual method for educational purposes.
Method 1: The Recommended Approach using python-daemon
This library handles all the complexities of creating a proper Unix daemon, such as forking, changing the working directory, setting file descriptors, and managing the process ID (PID) file.
Step 1: Install the Library
pip install python-daemon
Step 2: Create Your Daemon Script
Let's create a simple daemon script named my_daemon.py. This script will:

- Log a message every 5 seconds to a log file.
- Run in a loop until a specific "stop" file is created.
- Be properly managed by the
python-daemonlibrary.
# my_daemon.py
import daemon
import daemon.pidfile
import time
import os
import logging
import sys
# --- Configuration ---
LOG_FILE = '/tmp/my_daemon.log'
PID_FILE = '/tmp/my_daemon.pid'
STOP_FILE = '/tmp/stop_my_daemon' # File to signal the daemon to stop
# --- Logging Setup ---
# Configure logging to write to a file.
# The 'a' mode means the log file will be appended to if it already exists.
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def main_program_logic():
"""
This is the core logic of your daemon.
It will be run in a loop.
"""
logging.info("Daemon started.")
try:
while True:
# Check for a stop signal
if os.path.exists(STOP_FILE):
logging.info("Stop signal received. Shutting down.")
break
# Your daemon's work goes here
logging.info("Daemon is running...")
time.sleep(5)
except Exception as e:
logging.error(f"An error occurred: {e}", exc_info=True)
finally:
logging.info("Daemon stopped.")
# Clean up the stop file
if os.path.exists(STOP_FILE):
os.remove(STOP_FILE)
if __name__ == '__main__':
# The 'with' block ensures that the daemon context is properly managed.
# When the block is exited, the daemon will be detached.
with daemon.DaemonContext(
# The pidfile is crucial for managing the daemon's lifecycle.
# It prevents multiple instances from running.
pidfile=daemon.pidfile.PIDLockFile(PID_FILE),
# Optional: Change the working directory to the root.
# This prevents the daemon from holding onto a directory that might be unmounted.
working_directory='/',
# Optional: Redirect standard streams to /dev/null.
# This is standard practice for daemons to prevent them from
# accidentally interacting with a terminal.
stdout=sys.stdout,
stderr=sys.stderr,
stdin=sys.stdin
):
main_program_logic()
Step 3: Manage the Daemon
You can now use standard Python scripts to start, stop, and check the status of your daemon.
manage_daemon.py
# manage_daemon.py
import os
import time
import sys
import subprocess
PID_FILE = '/tmp/my_daemon.pid'
LOG_FILE = '/tmp/my_daemon.log'
DAEMON_SCRIPT = 'my_daemon.py'
def start_daemon():
"""Start the daemon in the background."""
if is_running():
print("Daemon is already running.")
return
# Use '&' to run the script in the background
# The 'nohup' command ensures the process keeps running after the terminal is closed.
# We redirect output to the log file.
command = f"nohup python3 {DAEMON_SCRIPT} > {LOG_FILE} 2>&1 &"
os.system(command)
# Give it a moment to start and write the PID file
time.sleep(1)
if is_running():
print(f"Daemon started. PID: {get_pid()}")
else:
print("Failed to start daemon.")
def stop_daemon():
"""Stop the daemon."""
pid = get_pid()
if not pid:
print("Daemon is not running.")
return
try:
# Send a SIGTERM signal for a graceful shutdown
os.kill(pid, 15) # SIGTERM
print(f"Daemon with PID {pid} has been stopped.")
except ProcessLookupError:
# The process was not found, maybe it crashed or already stopped.
# Clean up the stale PID file.
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
print("Daemon was not running. Stale PID file removed.")
def status_daemon():
"""Check the status of the daemon."""
if is_running():
print(f"Daemon is running. PID: {get_pid()}")
else:
print("Daemon is not running.")
def is_running():
"""Check if the daemon process is currently running."""
pid = get_pid()
if not pid:
return False
# Check if the process with the given PID still exists
try:
os.kill(pid, 0) # Sending signal 0 doesn't kill the process, just checks existence
return True
except OSError:
# Process does not exist
if os.path.exists(PID_FILE):
os.remove(PID_FILE) # Clean up stale PID file
return False
def get_pid():
"""Read the PID from the PID file."""
try:
with open(PID_FILE, 'r') as f:
return int(f.read().strip())
except (IOError, ValueError):
return None
if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python manage_daemon.py <start|stop|status>")
sys.exit(1)
command = sys.argv[1]
if command == 'start':
start_daemon()
elif command == 'stop':
stop_daemon()
elif command == 'status':
status_daemon()
else:
print("Invalid command. Use 'start', 'stop', or 'status'.")
How to Use It
-
Start the Daemon:
python3 manage_daemon.py start
Output:
Daemon started. PID: 12345
(图片来源网络,侵删) -
Check its Status:
python3 manage_daemon.py status
Output:
Daemon is running. PID: 12345 -
Check the Log File:
tail -f /tmp/my_daemon.log
You should see log messages appearing every 5 seconds.
-
Stop the Daemon:
python3 manage_daemon.py stop
Output:
Daemon with PID 12345 has been stopped.The log file will show the "Shutting down" message.
Method 2: The Manual Approach (For Educational Purposes)
Understanding how a daemon works is valuable. The python-daemon library essentially automates these steps. Here is the logic of what a manual daemonization process looks like.
A proper daemonization involves several steps:
- Fork the Parent Process: The parent process forks a child and then exits. This detaches the daemon from the controlling terminal.
- Become a Session Leader: The child process creates a new session and becomes its leader. This decouples it from the old terminal.
- Change Working Directory: Change the current working directory to to prevent the daemon from holding a mount point.
- Reset File Descriptors: Close standard file descriptors (
stdin,stdout,stderr) and redirect them to/dev/nullto prevent any unintended I/O. - Create a PID File: Write the process ID to a file so the daemon can be managed.
Here is a simplified version of my_daemon.py that does this manually.
# my_daemon_manual.py
import os
import time
import sys
import logging
PID_FILE = '/tmp/my_daemon_manual.pid'
LOG_FILE = '/tmp/my_daemon_manual.log'
def daemonize():
"""
The manual daemonization process.
"""
# 1. Fork the first time to become a daemon parent.
try:
if os.fork() > 0:
sys.exit(0) # Parent exits
except OSError as e:
sys.stderr.write(f"Fork #1 failed: {e}\n")
sys.exit(1)
# 2. Decouple from parent environment.
os.chdir('/')
os.setsid()
os.umask(0)
# 3. Fork a second time to ensure the daemon cannot re-acquire a terminal.
try:
if os.fork() > 0:
sys.exit(0) # Second parent exits
except OSError as e:
sys.stderr.write(f"Fork #2 failed: {e}\n")
sys.exit(1)
# 4. Redirect standard file descriptors.
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# 5. Write the PID file.
pid = str(os.getpid())
with open(PID_FILE, 'w+') as f:
f.write(pid + '\n')
def main():
# Setup logging after daemonizing
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logging.info("Manual daemon started.")
try:
while True:
logging.info("Daemon is running...")
time.sleep(5)
except KeyboardInterrupt:
logging.info("Manual daemon stopped by user.")
except Exception as e:
logging.error(f"An error occurred: {e}", exc_info=True)
finally:
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
logging.info("Manual daemon stopped.")
if __name__ == '__main__':
# The main script first calls the daemonize function
daemonize()
# After daemonize() returns, we are in the daemon process
main()
Why you should avoid this: The manual method is error-prone. You have to handle signals correctly, manage the PID file yourself, and ensure your code is robust. The python-daemon library has solved all these problems and is the industry standard for a reason.
Summary: python-daemon vs. Manual
| Feature | python-daemon (Recommended) |
Manual Method |
|---|---|---|
| Ease of Use | High. Simple with block. |
Low. Requires deep OS knowledge. |
| Robustness | High. Battle-tested and handles edge cases. | Low. Prone to subtle errors. |
| Code Clarity | High. Daemon logic is separate from setup logic. | Low. Daemonization logic is mixed with main logic. |
| PID File | Managed automatically. | Must be managed manually. |
| Signal Handling | Better handled by the library context. | Must be implemented manually. |
| When to Use | For almost all production daemons. | For learning purposes or in environments where you cannot add dependencies. |
