This tutorial is different from other Tutorials as of August 2015 since it closes other ports, doesn’t use secondary web server for letsencrypt config, tomcat is configured with it’s special script, using a tomcat from its source and uses tomcat native with APR.
This tutorial is complete, from fresh server install (i.e. Ubuntu Minimal as installed on dedicated server) to the working server.
The base for this tutorial is the following:
Configure glassfish as non root user on Ubuntu: https://www.nabisoft.com/tutorials/glassfish/installing-glassfish-41-on-ubuntu
Configure letsencrypt and tomcat/jira using non-native connector: http://blog.ivantichy.cz/blogpost/view/74
Configure tomcat with APR: http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html
Set locale
sudo locale-gen "en_US.UTF-8"
sudo dpkg-reconfigure locales
Put to file /etc/environment:
LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
Update environment
source /etc/environment
Install Java
apt-get install joe nano command-not-found python-software-properties software-properties-common
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
# nano /etc/environment
After opening with the text editor, we’ll need to add the following line into the bottom of the file.
JAVA_HOME="/usr/lib/jvm/java-8-oracle"
Once, the line is added, we’ll need to reload file.
source /etc/environment
check if java is working with java –version
Setup user going to run Tomcat
For legacy reasons, I call this user glassfish 😉
sudo
adduser --home
/home/glassfish
--system --shell
/bin/bash
glassfish
sudo
groupadd glassfishadm
Close ports and setup port forwarding
Since Tomcat process will be running as non-root user for security concerns, let’s create
iptables.rules file with the following:
#!/bin/bash
iptables -F
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
iptables -A INPUT -p tcp --dport 8181 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --
set
-j ACCEPT
iptables -I INPUT -p tcp -i eth0 --dport 22 -m state --state NEW -m recent --name sshprobe --update --seconds 60 --hitcount 3 --rttl -j DROP
iptables -A INPUT -j DROP
iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A OUTPUT -o lo -p tcp --dport 443 -j REDIRECT --to-port 8181
iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -A PREROUTING -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 8181
iptables-save >
/etc/my-iptables
.rules
iptables-restore <
/etc/my-iptables
.rules
# iptables -L
Make this file executable
chmod
+x iptables.rules
Invoke ./iptables.rules than create File: /etc/network/if-pre-up.d/iptablesload with the content:
#!/bin/sh
/sbin/iptables-restore
<
/etc/my-iptables
.rules
exit
0
File: /etc/network/if-post-down.d/iptablessave
#!/bin/sh
/sbin/iptables-save
-c >
/etc/my-iptables
.rules
if
[ -f
/etc/iptables
.downrules ];
then
/sbin/iptables-restore
<
/etc/iptables
.downrules
fi
exit
0
Set these files as executable
sudo
chmod
+x
/etc/network/if-post-down
.d
/iptablessave
sudo
chmod
+x
/etc/network/if-pre-up
.d
/iptablesload
You can reboot to see with iptables -L if the new config is OK
Download Tomcat from source and install it
su glassfish
As a user glassfish download Tomcat from source
cd ~
wget http://www-eu.apache.org/dist/tomcat/tomcat-8/v8.5.5/bin/apache-tomcat-8.5.5.tar.gz
tar zxvf apache-tomcat-8.5.5.tar.gz
exit
Exit the user glassfish. Create an init script like this (as a root):
root@condor1796 ~ # cat /etc/init.d/tomcat
#! /bin/sh
### BEGIN INIT INFO
# Provides: tomcat
# Required-Start: $local_fs $network
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Tomcat
# Description: Tomcat webserver running as non root user
### END INIT INFO
#to prevent some possible problems
export AS_JAVA=/usr/lib/jvm/java-8-oracle
#GLASSFISHPATH=/home/glassfish/bin
CATALINA_HOME=/home/glassfish/apache-tomcat-8.5.5
export CATALINA_OPTS="$CATALINA_OPTS -Xmx3500m -Djdk.tls.ephemeralDHKeySize=2048"
case "$1" in
start)
echo "starting tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh start
;;
restart)
$0 stop
#pause here is needed since Tomcat stop is apparently working in the background
sleep 0.1s
killall -9 java
$0 start
;;
stop)
echo "stopping tomcat from $CATALINA_HOME"
sudo -u glassfish $CATALINA_HOME/bin/catalina.sh stop
;;
*)
echo $"usage: $0 {start|stop|restart}"
exit 3
;;
esac
:
Set this script running in run levels:
sudo
chmod
a+x
/etc/init
.d
/tomcat
sudo
update-rc.d tomcat defaults
sudo
/etc/init
.d
/apache2
stop
update-rc.d -f apache2 remove
Get SSL certificate using Letsencrypt
Following https://certbot.eff.org/lets-encrypt/ubuntuxenial-other
Add multiverse repository to Ubuntu :
sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot
Run letsencrypt to generate a certificate if you already don’t have a running server
certbot certonly --manual -d online-utility.org -d www.online-utility.org --agree-tos --email mladen.adamovic@gmail.com
If you have a running server, read below about ACME support servlet and script for auto renewals but don’t forget after you finished certificates to make it readable by other users since user glassfish is going to need it.
Make certificated readable by other users:
chmod o+rx /etc/letsencrypt
chmod o+rx /etc/letsencrypt/archive
chmod o+rx /etc/letsencrypt/archive/online-utility.org
chmod o+rx /etc/letsencrypt/live
Also, you might need a script which fixes file permissions fix_letsencrypt_chmod.sh:
#!/bin/bash
chmod o+rx /etc/letsencrypt
chmod -R o+rx /etc/letsencrypt/*
You might need to run this if letsencrypt (certbot) messes up file rights for other users (glassfish in this case).
Setup Tomcat Native (APR) to enable a native connector
This part is from http://www.sheroz.com/pages/blog/installing-apache-tomcat-native-linux-ubuntu-1204.html
Install needed files
apt-get install build-essential libapr1 libapr1-dev
apt-get install make gcc openssl libssl-dev
Install APR package, from http://apr.apache.org/
wget http://www-us.apache.org/dist/apr/apr-1.5.2.tar.gz
tar zxvf apr-1.5.2.tar.gz
cd apr-1.5.2
sudo ./configure
sudo make
sudo make install
You should see the compiled file as
/usr/local/apr/lib/libapr-1.a
Download, compile and install Tomcat Native source package tomcat-native
wget http://www-us.apache.org/dist/tomcat/tomcat-connectors/native/1.2.8/source/tomcat-native-1.2.8-src.tar.gz
tar -xzf tomcat-native-1.2.8-src.tar.gz
cd tomcat-native-1.2.8-src/native
./configure --with-apr=/usr/local/apr --with-java-home=$JAVA_HOME --prefix=/home/glassfish/apache-tomcat-9.0.68
make make install
Create or edit the $CATALINA_HOME/bin/setenv.sh file with following line:
export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'
6. Restart tomcat and enjoy the desired result.
7. Configure Tomcat with APR
<Connector port="8181" protocol="org.apache.coyote.http11.Http11AprProtocol"
SSLCertificateFile="/etc/letsencrypt/live/online-utility.org/cert.pem"
SSLCertificateKeyFile="/etc/letsencrypt/live/online-utility.org/privkey.pem"
SSLCertificateChainFile="/etc/letsencrypt/live/online-utility.org/chain.pem"
SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"
connectionTimeout="20000" acceptCount="30000"
acceptorThreadCount="2"
compression="on" maxConnections="50000" maxThreads="400"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg"
useSendfile="false"
maxHttpHeaderSize="16392" SSLEnabled="true"
enableLookups="false"
scheme="https" secure="true" clientAuth="false" useBodyEncodingForURI="true"
/>
Enable HSTS in Tomcat
uncomment and edit lines in ~glassfish/apache-tomcat/conf/web.xml:
<filter>
<filter-name>httpHeaderSecurity</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<init-param>
<param-name>hstsMaxAgeSeconds</param-name>
<param-value>31536000</param-value>
</init-param>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>httpHeaderSecurity</filter-name>
<url-pattern>/*</url-pattern>
<url-pattern>*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Configure HTTP redirect application with support to ACME challenge
I’m using a web app with the one server which forwards HTTP to HTTPS but accepts ACME challenge:
package redirect;
import commons.FilesOperations;
import commons.UsualHtmlUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author mladen
*/
@WebServlet(name = "RedirectToHttpsWithAcme", urlPatterns = {"/*", "/"})
public class RedirectToHttpsWithAcme extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String requestUrl = request.getRequestURL().toString();
if (requestUrl.contains(".well-known/acme-challenge/")) {
int indexFilename = requestUrl.lastIndexOf("/") + 1;
boolean wasError = true;
if (indexFilename > 0 && indexFilename < requestUrl.length()) {
String filename = requestUrl.substring(indexFilename);
File existingFile = new File("/tmp/letsencrypt/public_html/.well-known/acme-challenge/" + filename);
if (existingFile.exists()) {
response.setContentType("text/plain");
OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(existingFile);
FilesOperations.inputStreamToOutputStream(in, out);
wasError = false;
}
}
if (wasError) {
throw new ServletException("invalid requestUrl " + requestUrl);
}
} else {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
int indexOfSlash = requestUrl.indexOf("//");
if (indexOfSlash > 0) {
String redirectUrl = "https:" + requestUrl.substring(indexOfSlash);
String queryString = request.getQueryString();
if (queryString != null && queryString.length() > 0) {
redirectUrl += "?" + UsualHtmlUtils.encodeURL(queryString);
}
response.setHeader("Location", redirectUrl);
} else {
throw new ServletException("invalid requestUrl " + requestUrl);
}
}
}
// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Handles the HTTP <code>POST</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Returns a short description of the servlet.
*
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}
It’s important to have Tomcat configured with this application (I call it appBaseRedirectToHttpsWithAcme) on port 8080 (since port 80 is forwarded to port 8080) in our iptables configuration:
<Service name="CatalinaHttp">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000" acceptCount="30000"
acceptorThreadCount="2"
compression="on" maxConnections="50000" maxThreads="100"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,image/svg+xml,image/svg"
useSendfile="false" />
<Engine name="CatalinaHttp" defaultHost="localhost">
<Host name="www.online-utility.org" appBase="appBaseRedirectToHttpsWithAcme" unpackWARs="true" autoDeploy="true">
<Alias>www.online-utility.org</Alias>
<Alias>new.online-utility.org</Alias>
<Alias>online-utility.org</Alias>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="http_www_online_utility_org_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
Setup a script for letsencrypt auto renewals
Lets call this script renew_cert.sh
#!/bin/bash
letsencrypt certonly --webroot --webroot-path /tmp/letsencrypt/public_html -d online-utility.org -d www.online-utility.org -d ww2.online-utility.org -d new.online-utility.org --agree-tos --email mladen.adamovic@
if [ $? != 0 ]; then
date | mail -s "Lets encrypt renew certificate fails for online-utility.org" mladen.adamovic@gmail.com
else
/root/fix_letsencrypt_chmod.sh
/etc/init.d/tomcat restart
fi
Don’t forget to make it executable:
chmod +x renew_cert.sh
Ensure this script is working as intended:
./renew_cert.sh
When it is working, it is time to put it into a cron job:
crontab -e
add the line to run it each month:
# m h dom mon dow command
0 10 1 * * /root/renew_cert.sh
Increase limits for heavy-duty websites
If you are going to have a large number of connections per second, don’t forget to add to /etc/security/limits.conf
* soft nofile 1000000
* hard nofile 1000000
root soft nofile 1000000
root hard nofile 1000000
Optional addons
Don’t forget other Linux stuff you might want like
apt-get install smartmontools
apt-get install postfix
If you are going to send email from Tomcat, don’t forget to add javax.mail.jar to /lib
Also to configure postfix to use letsencrypt certificates, please look up: https://www.upcloud.com/support/secure-postfix-using-lets-encrypt/
If you seriously want to send emails, you might need DKIM support (these private/public keys will be different from letsencrypt keys, it’s self-signed: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy )
Also, limit the number of emails send by adding to /etc/postfix/main.cf :
smtp_destination_concurrency_limit = 2
smtp_destination_rate_delay = 1s
smtp_extra_recipient_limit = 10
default_destination_concurrency_limit=2
default_destination_rate_delay=1s
Rember to setup a monitoring, for that purposes I’m using self-monitoring script as a cron job.
Congratulations
Everything should be setup now. For help, I’d suggest ask on askubuntu.com and Tomcat mailing list (depending of the actual) problem.