Daemonize a Java service on Linux the simple way

Creating a Java-based service on Linux that will start when the operating system boots and run continuously doesn’t require any extra libraries, despite many internet posts to the contrary.

I wrote a web application using an embedded Jetty server that I needed to run as a service on Ubuntu 14.04.

In a previous project we needed to do the same thing on Windows and we used Java Service Wrapper (JSW), but then Tanuki Software changed their free license to GPL, which we could not use for a commercial application. We switched to Yet Another Java Service Wrapper (YAJSW), which has a more friendly license and uses the same configuration file format as JSW. This all worked fine, but in my latest project, I was deploying on Ubuntu 14.04, trying to build everything from Maven, and trying to keep everything as simple as possible. I found that the Appassembler Maven Plugin will generate a JSW configuration file, but there were too many moving parts for my taste.

Next I tried Apache Commons Daemon (jsvc), which was easy to integrate and worked fine with Maven, but I found the commandline very finicky. It took a long time to get the commandline arguments correct. I was attracted to jsvc because it was used by Tomcat, but then I found a post that said they removed it from Tomcat deployed on Ubuntu and Debian.

There is a much simplier way.

The Answer

First, I implemented a Java shutdown hook so when the process receives an interrupt, the service will exit cleanly. This feature is built into the JVM. The shutdown hook can be tested by running the application and then pressing Control-C in the console.

Second, I added a plugin to my pom.xml to use Appassembler to produce a shell script that will run my Java program. The plugin configuration looked something like this:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>appassembler-maven-plugin</artifactId>
  <version>1.10</version>
  <configuration>
    <assembleDirectory>${project.build.directory}/assembly</assembleDirectory>
    <!-- Generate bin scripts for windows and unix pr default -->
    <repositoryName>lib</repositoryName>
    <repositoryLayout>flat</repositoryLayout>
    <platforms>
      <platform>unix</platform>
    </platforms>
    <configurationDirectory>config</configurationDirectory>
    <configurationSourceDirectory>configs/production</configurationSourceDirectory>
    <copyConfigurationDirectory>true</copyConfigurationDirectory>
    <programs>
      <program>
        <mainClass>com.webappsthatwork.WebappMain</mainClass>
        <id>web-server</id>
      </program>
    </programs>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>assemble</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The Appassembler plugin is nice because it automatically handles:

  • copying all dependent libraries
  • creating a shell script with the appropriate classpath
  • passing system properties for the application’s home directory

Now I had a simple shell script to run which starts the web application.

Third, I wrote an Upstart configuration file. My service was called web, so the file is called web.conf:

description "Web Server"

  start on runlevel [2345]
  stop on runlevel [!2345]
  respawn
  respawn limit 10 5

  setuid web
  setgid web
  umask 027
  chdir /opt/web
  console none
  
  # Modify these options as needed
  env JAVA_OPTS=""
  
  exec authbind --deep /opt/web/bin/web-server

The file above does the following:

  • Restarts the server if it dies, but if it restarts more than 10 times in 5 seconds, the system will not try to restart it again.
  • The owner and group of the process are both set to web. This avoids running the process as root.
  • The process umask is 027, which means any log files created will be writable by user web, readable by group web, and have no access by anybody else.
  • The process runs out of the directory /opt/web
  • All console output is redirected to /dev/null
  • The environment has the variable JAVA_OPTS added to it, which the generated shell script uses for additional JVM arguments.

For more details, there is good documentation of the Upstart stanzas.

Also, notice that the service itself is launched using authbind. This allows the service to run as user web, but have permission to bind to ports 80 and 443.

All I did was copy the web.conf file to /etc/init and then run

# service web start

Conclusion

I found this method very straightforward. If I find later that I’m missing something and JSW, YAJSW, or jsvc add something I was missing, I’ll update this post.