Stop Using nohup java -jar: How to Keep a Spring Boot App Alive in Production
In the development environment, we're used to casually typing:
nohup java -jar my-app.jar &
It does run. But in a production environment, this approach is like walking a tightrope over a high-voltage line.
Why nohup & Doesn't Cut It
nohup plus & only puts the process in the background and ignores the hangup signal; it is fundamentally not a process management solution. Using this in production, you will frequently encounter these pitfalls:
- Chaotic process management: Want to restart? First
psto find the PID, thenkill. A slip of the hand could accidentally kill other services. - No automatic startup on boot: If the server restarts due to a failure, the service won't recover automatically. Being woken up by an alarm call in the middle of the night is the norm.
- Loss of permission control: Running directly as
rootfor convenience poses an extremely high security risk. - Chaotic log management: Log files have no rotation; filling up the disk is only a matter of time.
Behind the eight characters "Deploy Spring Boot project on Linux" lies a practical chain spanning four dimensions: development, operations, security, and performance. You need to use systemd for service supervision, not just tough it out with
nohup &.
Three production-grade solutions are introduced below, ranked by recommendation level.
Solution 1: Systemd (Most Recommended)
systemd is the standard service manager for modern Linux distributions (CentOS 7+, Ubuntu 16.04+). Using it to manage Spring Boot applications requires no additional software installation, yet its capabilities are far more powerful than nohup.
Step 1: Create a Dedicated Account (Security First)
Absolutely never run business code as the root user:
# Create a user without login permissions
sudo useradd -r -s /bin/false app_user
# Ensure this user has read/write permissions for the JAR file
sudo chown app_user:app_user /opt/myapp/app.jar
Step 2: Write the Service File
Create a service configuration file at /etc/systemd/system/myapp.service:
[Unit]
Description=My Spring Boot Application
After=syslog.target network.target
[Service]
# Specify the running user to achieve permission isolation
User=app_user
Group=app_user
# Core startup command (recommend specifying the full Java path and JVM parameters)
ExecStart=/usr/bin/java -Xms512m -Xmx512m -XX:+UseG1GC \
-jar /opt/myapp/app.jar \
--spring.profiles.active=prod
# Graceful shutdown for Spring Boot: 143 represents a normal exit via SIGTERM signal
SuccessExitStatus=143
# Automatic restart on crash
Restart=always
RestartSec=10
# Logs are managed by systemd, view with journalctl
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Step 3: Activation and Management
# Reload configuration
sudo systemctl daemon-reload
# Enable automatic startup on boot (say goodbye to restart anxiety)
sudo systemctl enable myapp
# Start the service
sudo systemctl start myapp
# Check status
sudo systemctl status myapp
# View logs
sudo journalctl -u myapp -f
What If You Don't Have Root Privileges?
Many internal network environments in banks and state-owned enterprises only provide regular accounts without sudo permissions. In this case, you can use systemd's User Mode:
# Create user-level service file directory
mkdir -p ~/.config/systemd/user/
# Place the .service file in this directory, with the same content but remove the User/Group fields
# Enable and start
systemctl --user enable myapp
systemctl --user start myapp
# Key step: Enable lingering so the service continues running after the user logs out
# This requires an administrator to execute once:
sudo loginctl enable-linger <username>
Solution 2: Supervisor
If your system is still using an older CentOS 6, or you need a lightweight, cross-language process management tool, Supervisor is a good choice.
Installation and Configuration
# Ubuntu/Debian
sudo apt install supervisor
# CentOS
sudo yum install supervisor
Create the configuration at /etc/supervisor/conf.d/myapp.conf:
[program:myapp]
command=/usr/bin/java -Xms512m -Xmx512m -jar /opt/myapp/app.jar --spring.profiles.active=prod
directory=/opt/myapp
user=app_user
autostart=true
autorestart=true
startsecs=10
stopasgroup=true
killasgroup=true
stopwaitsecs=30
redirect_stderr=true
stdout_logfile=/var/log/myapp/out.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
environment=JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
Several key configurations must be noted:
commandmust use an absolute path for the Java command; Supervisor does not inherit the system's$PATH.autorestart=true+startsecs=10: Tells Supervisor this is a long-running service; don't kill it just because startup is slow.stopasgroup=true+killasgroup=true: Ensures the entire process group can be killed; otherwise, child threads from thread pools or Netty might remain.
Common Management Commands
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start myapp
sudo supervisorctl status
Note: On CentOS 7+, systemd has become the standard, and Supervisor is gradually being deprecated in some distributions. For new projects, prioritize systemd.
Solution 3: Docker + Kubernetes (The Cloud-Native Era Choice)
If your team has already embarked on the path of containerization, Docker + K8s is a more modern solution.
Dockerfile Example
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/myapp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xms512m", "-Xmx512m", "-jar", "app.jar"]
Spring Boot 3.x provides good support for containerization, making containerized deployment very simple.
Advantages of Kubernetes
K8s itself is a production-grade process supervision system:
- Automatic restart: Pods are automatically recreated if they crash.
- Rolling updates: Zero-downtime releases.
- Health checks: liveness probe + readiness probe.
- Horizontal scaling: Automatic scaling based on load.
Docker solves environment consistency, K8s handles automated operations.
Production Environment Checklist
No matter which solution you choose, you must do the following:
| Check Item | Description |
|---|---|
| Dedicated Run User | Absolutely never run applications as root. |
| JVM Parameter Tuning | Specify -Xms, -Xmx, GC strategy; don't use defaults. |
| Specify Config File | Use --spring.config.location to externalize configuration; don't package it into the JAR. |
| Graceful Shutdown | SuccessExitStatus=143 allows Spring Boot to execute @PreDestroy normally. |
| Log Management | Configure log rotation (Logback's RollingPolicy) to prevent disk from filling up. |
| Auto-start on Boot | systemctl enable or autostart=true. |
| Firewall | Confirm that security groups and the system firewall allow the application port. |
| Java Version Match | Spring Boot 2.7.x uses JDK 8/17, 3.x mandates JDK 17+. |
Summary
| Solution | Applicable Scenarios | Advantages | Disadvantages |
|---|---|---|---|
| Systemd | Most Linux production environments | Native integration, powerful, no install | Linux only |
| Supervisor | Legacy systems, cross-language scenarios | Lightweight, flexible configuration | Gradually deprecated on CentOS 7+ |
| Docker + K8s | Cloud-native, microservices architecture | Environment consistency, automated ops | High learning curve, infrastructure demands |
One-sentence recommendation: If your server is CentOS 7+ or Ubuntu 16.04+, use systemd directly; if the company has already adopted containers, go with Docker + K8s; only consider Supervisor for special circumstances.
Stop letting the development-phase toy nohup java -jar run in production environments. What production needs is not "it can run," but "running rock-solid for 365 days."