Managing Services with systemd
Managing Services with systemd
When you start working with Linux servers, one of the first things you’ll encounter is the need to manage services - programs that run in the background to provide functionality like web servers, databases, or custom applications. Systemd is the modern service management system used by most Linux distributions today, and understanding it is crucial for any DevOps engineer.
Think of systemd as the traffic controller for your Linux system. It decides when services start, stop, and how they interact with each other. Unlike older systems that started services one by one in sequence, systemd can start multiple services simultaneously, making your system boot faster and run more efficiently.
What is systemd and Why Does It Matter?
Systemd replaced the older SysV init system and has become the standard across major Linux distributions like Ubuntu, CentOS, RHEL, and Debian. As a DevOps engineer, you’ll interact with systemd daily when deploying applications, troubleshooting issues, and maintaining server infrastructure.
The key advantages of systemd include:
Parallel Service Startup: Instead of waiting for each service to start completely before starting the next one, systemd can start multiple services at once, significantly reducing boot times.
Dependency Management: Systemd understands which services depend on others and ensures they start in the correct order. For example, your web application won’t try to start before the database it needs is running.
Comprehensive Logging: All service logs are centralized in one place, making troubleshooting much easier than hunting through multiple log files.
Automatic Restart Capabilities: Services can be configured to automatically restart if they crash, improving system reliability.
Resource Management: You can limit how much CPU, memory, or other resources a service can use, preventing one misbehaving service from bringing down your entire system.
Understanding systemd Units
In systemd terminology, everything it manages is called a “unit.” There are different types of units for different purposes:
Service Units are the most common type and represent the services (programs) running on your system. These end with .service and include things like web servers, databases, and your custom applications.
Target Units group related services together, similar to the old concept of runlevels. For example, the multi-user.target represents the state where the system is ready for multiple users to log in.
Timer Units are systemd’s equivalent to cron jobs, allowing you to schedule tasks to run at specific times or intervals.
Socket Units allow services to start only when someone tries to connect to them, saving system resources.
Understanding these unit types helps you grasp how systemd organizes and manages different aspects of your system.
Basic Service Management
Let’s start with the fundamental operations you’ll perform daily. The main command for interacting with systemd is systemctl.
Checking Service Status
The most common task is checking whether a service is running:
systemctl status nginx
This command shows you whether the nginx web server is active (running), how long it’s been running, and recent log messages. The output uses color coding: green usually means everything is working, red indicates problems.
You can also get a quick yes/no answer about whether a service is running:
systemctl is-active nginx
This returns either “active” or “inactive” without the detailed information.
Starting and Stopping Services
To start a service that isn’t running:
systemctl start nginx
This starts the service immediately but doesn’t configure it to start automatically when the system boots.
To stop a running service:
systemctl stop nginx
To restart a service (stop it completely, then start it again):
systemctl restart nginx
Sometimes you want to reload a service’s configuration without fully stopping it:
systemctl reload nginx
This tells the service to re-read its configuration files while continuing to run. Not all services support this operation.
Enabling Services for Boot
Starting a service manually only keeps it running until the system reboots. To make a service start automatically when the system boots:
systemctl enable nginx
This creates the necessary links so systemd knows to start this service during boot. The service isn’t started immediately - you need to use start for that.
To prevent a service from starting at boot:
systemctl disable nginx
You can check if a service is enabled for boot:
systemctl is-enabled nginx
Getting Service Information
To see all available services on your system:
systemctl list-units --type=service
This shows all services, their current state, and a brief description. You can also see services that failed to start or have other issues.
To see detailed information about a specific service:
systemctl show nginx
This displays all configuration options and current values for the service, which is useful for troubleshooting.
Understanding Service Configuration Files
Every service is defined by a configuration file called a unit file. These files tell systemd how to start the service, what user it should run as, what other services it depends on, and much more.
Where Unit Files Live
Systemd looks for unit files in several locations, with a specific priority order:
System unit files (/lib/systemd/system/ or /usr/lib/systemd/system/) contain the default configurations installed by packages. You shouldn’t modify these directly because package updates might overwrite your changes.
Local unit files (/etc/systemd/system/) are where you place custom unit files or modifications to existing ones. These take priority over system unit files.
Runtime unit files (/run/systemd/system/) are created dynamically and exist only while the system is running.
Anatomy of a Unit File
Unit files are organized into sections, each with a specific purpose. Let’s examine a typical service unit file:
The [Unit] section contains general information about the service:
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target
The Description is a human-readable explanation of what the service does. The After directive tells systemd to start this service only after the network is ready, remote filesystems are mounted, and name service lookups are working.
The [Service] section defines how the service should run:
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
User=nginx
Group=nginx
Let’s break this down:
Type=forkingmeans the main process will fork (create a copy of itself) and the parent will exit, leaving the child running in backgroundPIDFiletells systemd where to find the process ID of the main service processExecStartPreruns a command before starting the main service (here, testing the nginx configuration)ExecStartis the actual command to start the serviceExecReloaddefines how to reload the service configurationUserandGroupspecify which user account the service should run under
The [Install] section determines when and how the service should be enabled:
[Install]
WantedBy=multi-user.target
This means when you enable the service, it will be started as part of the multi-user target (normal system operation).
Service Types
The Type parameter in the service section is crucial for understanding how systemd manages your service:
simple (the default) means your program runs in the foreground and doesn’t fork. This is the most straightforward type for most applications.
forking means your program will start, create a background process, and then the original process exits. Traditional Unix daemons often work this way.
oneshot is for programs that run once and exit, like initialization scripts or backup jobs.
notify is for programs that can tell systemd when they’re fully ready to serve requests.
Understanding these types helps you choose the right configuration for your services.
Working with Dependencies
One of systemd’s most powerful features is its ability to manage service dependencies. This ensures services start in the right order and that required services are available when needed.
Types of Dependencies
Requires creates a strong dependency. If the required service fails to start, this service won’t start either. If the required service stops unexpectedly, this service will also be stopped.
Wants creates a weaker dependency. Systemd will try to start the wanted service, but if it fails, this service can still start.
After and Before control ordering. After=network.target means “start this service after the network is ready,” but doesn’t require the network to be working.
Practical Example
Consider a web application that needs a database:
[Unit]
Description=My Web Application
After=network.target
Wants=postgresql.service
This configuration tells systemd:
- Start this service after the network is ready
- Try to start PostgreSQL, but don’t fail if it’s not available (maybe the database is on another server)
For a more critical dependency:
[Unit]
Description=My Web Application
After=network.target postgresql.service
Requires=postgresql.service
Now the web application requires PostgreSQL to be running and will fail to start if PostgreSQL isn’t available.
Managing Logs with journald
Systemd includes its own logging system called journald, which captures all output from services and system events in one centralized location.
Viewing Service Logs
To see logs for a specific service:
journalctl -u nginx
This shows all log entries for the nginx service. The output includes timestamps, the service name, and the actual log messages.
To follow logs in real-time (like tail -f):
journalctl -u nginx -f
This is incredibly useful for watching what happens when you test your application or troubleshoot issues.
Filtering Logs
You can filter logs by time period:
journalctl -u nginx --since today
journalctl -u nginx --since "2023-01-01 10:00:00"
journalctl -u nginx --since "1 hour ago"
You can also filter by priority level:
journalctl -u nginx -p err
This shows only error messages, filtering out informational messages that might clutter the output.
Understanding Log Priorities
Systemd uses standard syslog priority levels:
- emerg (0): System is unusable
- alert (1): Action must be taken immediately
- crit (2): Critical conditions
- err (3): Error conditions
- warning (4): Warning conditions
- notice (5): Normal but significant condition
- info (6): Informational messages
- debug (7): Debug-level messages
When troubleshooting, start with error and critical messages, then work your way down to informational messages if needed.
Creating Custom Services
As a DevOps engineer, you’ll often need to create custom service files for applications that don’t come with systemd integration.
Planning Your Service
Before creating a unit file, consider:
- What user should the service run as? (Never run services as root unless absolutely necessary)
- What other services does it depend on?
- Should it restart automatically if it crashes?
- What environment variables does it need?
Basic Custom Service Example
Let’s create a service for a simple web application:
[Unit]
Description=My Custom Web App
After=network.target
[Service]
Type=simple
User=webapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Let’s understand each part:
Unit Section:
- Describes what the service does
After=network.targetensures the network is ready before starting
Service Section:
Type=simplebecause our app runs in the foregroundUser=webappruns the service as a dedicated user (better security)WorkingDirectorysets where the process should run fromExecStartis the command to start the applicationRestart=alwaysmeans systemd will restart it if it crashesRestartSec=10waits 10 seconds before restarting
Install Section:
WantedBy=multi-user.targetmeans it starts during normal system operation
Implementing the Service
Save this file as /etc/systemd/system/myapp.service, then:
sudo systemctl daemon-reload
This tells systemd to scan for new or changed unit files.
sudo systemctl enable myapp
sudo systemctl start myapp
Then check that it’s working:
sudo systemctl status myapp
Security Considerations
Running services securely is crucial in production environments. Systemd provides many options to limit what services can do.
User and Group Isolation
Always run services as dedicated users with minimal privileges:
[Service]
User=myapp
Group=myapp
Create the dedicated user account:
sudo useradd --system --no-create-home --shell /bin/false myapp
This creates a system user that can’t log in and doesn’t have a home directory.
Filesystem Restrictions
You can limit what parts of the filesystem a service can access:
[Service]
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp/data
PrivateTmp=truegives the service its own private /tmp directoryProtectSystem=strictmakes most of the system read-onlyReadWritePathsexplicitly allows writing to specific directories
Resource Limits
Prevent services from consuming too many system resources:
[Service]
MemoryLimit=512M
CPUQuota=50%
This limits the service to 512MB of RAM and 50% of one CPU core.
Troubleshooting Common Issues
Service Won’t Start
When a service fails to start, the first step is checking its status:
systemctl status myapp
Look for error messages in the output. Common issues include:
- Configuration file syntax errors
- Missing dependencies
- Permission problems
- Port conflicts
Service Keeps Restarting
If a service keeps restarting, check the logs:
journalctl -u myapp -f
Look for error messages that indicate why the service is crashing. Common causes include:
- Application bugs
- Missing configuration files
- Database connection problems
- Port already in use
Permission Denied Errors
These often occur when:
- The service user doesn’t have permission to read configuration files
- The service user can’t write to log directories
- SELinux or AppArmor policies are blocking access
Check file permissions and ownership:
ls -la /etc/myapp/
ls -la /var/log/myapp/
Performance and Monitoring
Checking Resource Usage
Monitor how much CPU and memory your services are using:
systemctl status myapp
The status output includes basic resource usage information.
For more detailed monitoring:
systemctl show myapp | grep -E "(CPU|Memory)"
Log Management
Service logs can grow large over time. Configure log rotation to prevent disk space issues:
journalctl --vacuum-time=30d
This removes journal entries older than 30 days.
You can also limit the total size of logs:
journalctl --vacuum-size=1G
Integration with Modern DevOps Practices
Configuration Management
When managing multiple servers, use configuration management tools like Ansible to deploy and manage systemd services consistently:
- name: Deploy application service file
template:
src: myapp.service.j2
dest: /etc/systemd/system/myapp.service
notify:
- reload systemd
- restart myapp
Monitoring and Alerting
Integrate systemd with monitoring systems to get alerts when services fail:
systemctl is-failed myapp
This command returns a non-zero exit code if the service has failed, making it easy to check from monitoring scripts.
Container Integration
While containers are becoming popular, systemd is still useful for managing containerized applications:
[Unit]
Description=My Containerized App
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/usr/bin/docker run --name myapp -p 8080:8080 myapp:latest
ExecStop=/usr/bin/docker stop myapp
Restart=always
This manages a Docker container as a systemd service, providing the benefits of both technologies.
Best Practices for DevOps Engineers
Service Design
Keep it simple: Use Type=simple when possible. It’s easier to understand and debug.
Use dedicated users: Never run services as root unless absolutely necessary. Create dedicated system users for each service.
Handle signals properly: Ensure your applications handle SIGTERM gracefully for clean shutdowns.
Implement health checks: Build health check endpoints into your applications so you can verify they’re working correctly.
Configuration Management
Use environment files: Store configuration in separate files that can be managed independently:
[Service]
EnvironmentFile=/etc/myapp/environment
Document dependencies: Clearly document what other services your application needs and configure appropriate dependencies.
Version your unit files: Keep unit files in version control along with your application code.
Monitoring and Maintenance
Set up log monitoring: Configure log aggregation and alerting for error patterns in your service logs.
Monitor resource usage: Set appropriate limits and monitor actual usage to optimize performance.
Plan for failures: Configure appropriate restart policies and test failure scenarios.
Regular maintenance: Periodically review and clean up old services, update configurations, and rotate logs.
Conclusion
Mastering systemd is essential for modern DevOps work. It provides a powerful, unified interface for managing services, logs, and system resources. The key concepts to remember are:
Understanding unit files and how to configure them for your specific needs is fundamental. Service dependencies ensure your applications start in the right order and have the resources they need. Proper logging configuration and log analysis skills help you troubleshoot issues quickly.
Security should always be a priority - use dedicated users, filesystem restrictions, and resource limits to protect your systems. Regular monitoring and maintenance keep your services running smoothly and help you catch problems before they impact users.
Start with simple configurations and gradually add more advanced features as you become comfortable with the basics. Practice creating custom services for your applications, and always test changes in a safe environment before deploying to production.
Systemd might seem complex at first, but its consistency and power make it an invaluable tool for managing modern Linux infrastructure. The time invested in learning it well will pay dividends throughout your DevOps career.