Spring Boot is the most popular framework for building production-ready Java applications, and RHEL 8 provides an excellent foundation for running them as long-lived services. In this tutorial you will create a Spring Boot application using Maven, package it into a self-contained executable JAR, configure it to run as a systemd service with a dedicated user, and expose it through an Nginx reverse proxy. By the end you will have a fully operational Spring Boot deployment that starts automatically on boot and restarts on failure.
Prerequisites
- RHEL 8 with
sudoaccess and an active subscription or compatible repository - Java 17 installed — the Spring Boot 3.x line requires Java 17 or newer
- Apache Maven installed (
sudo dnf install -y maven) - Nginx installed (
sudo dnf install -y nginx) and the firewall port 80 open - Basic familiarity with systemd unit files
Step 1 — Create a Spring Boot Project
The fastest way to scaffold a Spring Boot project is with Spring Initializr. Use curl to download a pre-configured project archive directly to your server:
curl -s https://start.spring.io/starter.tgz
-d type=maven-project
-d language=java
-d bootVersion=3.2.5
-d baseDir=myapp
-d groupId=com.example
-d artifactId=myapp
-d javaVersion=17
-d dependencies=web,actuator
| tar -xzvf -
cd myapp
This creates a project with the Spring Web and Spring Boot Actuator starters. Actuator exposes management endpoints including a health check at /actuator/health which is useful for monitoring and load balancer probes.
Step 2 — Add a Simple REST Controller and Build the JAR
Create a minimal controller so the application returns a response at the root URL, then package it into a fat JAR:
cat > src/main/java/com/example/myapp/HelloController.java << 'EOF'
package com.example.myapp;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello from Spring Boot on RHEL 8!";
}
}
EOF
mvn package -DskipTests
The fat JAR — with all dependencies embedded — is written to target/myapp-0.0.1-SNAPSHOT.jar. Test it locally before deploying:
java -jar target/myapp-0.0.1-SNAPSHOT.jar &
curl http://localhost:8080/
curl http://localhost:8080/actuator/health
kill %1
Step 3 — Create a Dedicated Service User and Deploy the JAR
Running a service as root is a security risk. Create a system user and copy the JAR to a standard location:
sudo useradd -r -s /sbin/nologin springapp
sudo mkdir -p /opt/myapp
sudo cp target/myapp-0.0.1-SNAPSHOT.jar /opt/myapp/myapp.jar
sudo chown -R springapp:springapp /opt/myapp
Create an environment file to hold configuration secrets outside the JAR. This file should be readable only by root and the service user:
sudo tee /opt/myapp/myapp.env << 'EOF'
SPRING_PROFILES_ACTIVE=production
SERVER_PORT=8080
EOF
sudo chown root:springapp /opt/myapp/myapp.env
sudo chmod 640 /opt/myapp/myapp.env
Step 4 — Create the systemd Service Unit
Write a systemd unit file that starts the application at boot, restarts it on failure, and loads environment variables from the .env file:
sudo tee /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Spring Boot Application
After=network.target
[Service]
Type=simple
User=springapp
Group=springapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/opt/myapp/myapp.env
ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
Step 5 — Configure Nginx as a Reverse Proxy
Create an Nginx server block that forwards public HTTP traffic to the Spring Boot application listening on localhost:8080. Place the configuration in its own file under conf.d:
sudo tee /etc/nginx/conf.d/myapp.conf << 'EOF'
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /actuator {
deny all;
}
}
EOF
sudo nginx -t
sudo systemctl enable --now nginx
The deny all block on /actuator prevents the management endpoints from being publicly accessible. Replace example.com with your actual domain name.
Step 6 — Verify the Deployment
Confirm the service is running and responding correctly through Nginx:
sudo systemctl status myapp
sudo journalctl -u myapp -n 50 --no-pager
curl http://localhost/
curl http://localhost/actuator/health
The last curl should return {"status":"UP"} when accessed directly on port 8080, and a 403 when accessed through Nginx on port 80, confirming the actuator deny rule is in effect.
Conclusion
You have built a Spring Boot fat JAR with Maven, deployed it under a dedicated system user, configured it as a systemd service that auto-starts and self-heals, and placed Nginx in front of it as a reverse proxy with the actuator endpoints blocked from public access. This pattern is production-ready and forms the foundation for adding TLS termination with Certbot, blue-green deployments, or container-based orchestration as your application grows.
Next steps: How to Secure Nginx with Let’s Encrypt on RHEL 8, How to Install Maven and Gradle on RHEL 8, and How to Monitor Java Applications with Prometheus and Grafana on RHEL 8.