必须用 user= 和 group= 显式指定非 root 用户,否则默认以 root 运行;java 应用无需特权,root 权限会放大漏洞风险(如反序列化、jndi 注入),导致攻击者获得系统最高控制权,且违反最小权限原则。

必须用 User= 和 Group= 显式指定非 root 用户,否则默认以 root 运行 —— 这是安全红线,不是可选项。
为什么不能用 root 启动 Java 服务
Java 应用本身不需特权,root 权限反而放大风险:一旦 JVM 被利用(如反序列化漏洞、JNDI 注入),攻击者直接获得系统最高控制权。systemd 默认以 root 启动服务,除非你明确覆盖 User 和 Group。
- 常见错误现象:
ps aux | grep java显示进程 UID 是 0;日志里出现java.io.FileNotFoundException: /root/.config/...类路径错误 - 后果:SELinux 或 AppArmor 可能拦截非预期的 root 行为;审计日志里全是 root 操作,无法定位具体服务行为
- 真实场景:生产环境安全策略强制要求“最小权限原则”,CI/CD 流水线扫描会直接拒绝未设
User的.service文件
User= 必须指向已存在的系统用户,且该用户需有对应权限
systemd 不会自动创建用户,User=myapp 前必须确保系统里真有这个用户,且它对 jar 包、配置目录、日志路径都有读写权限。
- 创建专用用户(推荐):
sudo useradd -r -s /sbin/nologin myapp(-r表示系统用户,-s /sbin/nologin禁止登录) - 赋予权限:
sudo chown -R myapp:myapp /opt/myapp(jar、配置、日志目录全归该用户) - 验证是否能手动运行:
sudo -u myapp /usr/bin/java -jar /opt/myapp/app.jar—— 必须成功,否则 systemd 一定失败 - 注意:
User不能是用户名变量或环境变量,只能是字面量,如User=myapp,不能写成User=${APP_USER}
Java 进程实际运行身份由 User + WorkingDirectory + ExecStart 共同决定
只设 User 不够,三个字段必须协同。例如 WorkingDirectory=/opt/myapp 但该目录属主是 root,即使 User=myapp,Java 进程也会因无法读取配置文件而退出。
-
WorkingDirectory必须是该用户有读+执行(r-x)权限的目录(用于加载 classpath) -
ExecStart中的 jar 路径必须对该用户可读(r--),若含 native 库还需可执行(--x) - 日志写入路径(如 logback 配置中的
file)必须对该用户可写(rw-),否则日志静默丢失 - 性能影响:权限检查在每次启动时发生,若路径权限混乱,systemd 会卡在
start-pre阶段超时(默认 90 秒),报错Failed with result 'timeout'
容易被忽略的兼容性细节
某些 JDK 版本(尤其是 OpenJDK 8u202 之前)在非 root 用户下启动时,若 JAVA_HOME 指向全局 JDK 目录(如 /usr/lib/jvm/java-1.8.0-openjdk),可能因该目录权限太严(drwxr-x---)导致 JVM 初始化失败。
- 解决方法:改用
Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk并确保该目录对myapp用户可读可执行(chmod o+rx /usr/lib/jvm/java-1.8.0-openjdk) - 更稳妥做法:把 JDK 解压到
/opt/jdk并chown -R myapp:myapp /opt/jdk,然后在ExecStart中用绝对路径调用/opt/jdk/bin/java - CentOS 7/RHEL 7 上,若启用了 SELinux,还需运行
sudo setsebool -P tomcat_can_network_connect_db 1(如应用连数据库)或sudo semanage fcontext -a -t tomcat_exec_t "/opt/myapp/bin/java",否则会被静默拦截











