Jira Selfhosted w/ SSL

This is a guide on how to install JIRA on your own VPS, including a SSL Certificate from Letsencrypt without the Tomcat SSL Keyfile stuff (and also without apache or nginx as a proxy).

Requirements

  • Fully licensed JIRA (10$ for a lifetime license with 10 seats)
  • Public Domain
  • Virtual Private Server / Dedicated Server with a public IP
  • A Linux derivative OS (Debian/Ubuntu/CentOS/etc.)

Setup

Install JIRA based on the atlassian documentation

su root

wget https://product-downloads.atlassian.com/software/jira/downloads/atlassian-jira-software-7.12.1-x64.bin

chmod +x atlassian-jira-*
./atlassian-jira-*

# Follow the installation guide
Installation Directory: /opt/atlassian/jira
Home Directory: /var/atlassian/application-data/jira
HTTP Port: 8443
RMI Port: 8005
Install as service: Yes

ufw allow 8443/tcp
# postgresql
apt-get install -y postgresql
su postgres
psql
/* jira database */
CREATE USER jira_admin;
CREATE DATABASE jira;
ALTER USER jira_admin with encrypted password '<passwd>';
GRANT ALL PRIVILEGES ON DATABASE jira TO jira_admin;

create /etc/systemd/system/jira.service

# /etc/systemd/system/jira.service
[Unit]
Description=Jira Issue & Project Tracking Software
After=network.target

[Service]
Type=forking
User=jira
PIDFile=/opt/atlassian/jira/work/catalina.pid
ExecStart=/opt/atlassian/jira/bin/start-jira.sh
ExecStop=/opt/atlassian/jira/bin/stop-jira.sh

[Install]
WantedBy=multi-user.target

→ SSH into your server

# on your client pc:
ssh remote -L 8443:localhost:8443

→ [optional] setup a screen or tmux session for installation safety

→ Begin the SSL Hacking:

# FIRST: STOP JIRA
systemctl stop jira

# Update Package Lists
apt-get update

# open Port 22 (SSH) and enable the firewall
# Might be different when using CentOS/Fedora/etc.
ufw allow 22/tcp
ufw enable

# Set the following environment variables for a quicker setup
# The Path you specified for your jira installation
export JIRA_INSTALL="/opt/atlassian/jira"
# Your domain that either has a certifcate or needs one via Letsencrypt
export DOMAIN="jira.yourdomain.com"
# Your Email that you want to link to your Letsencrypt certificate
export EMAIL="foo@bar.com"
# Locale Language for the installation (optional, but nice to have)
export LC_ALL="en_US.utf8"

Tomcat APR

→ To use SSL Certificates without the tomcat keystore crap, we need the apache portable runtime.

Unfortunately, this is only available when building on our own. I see no problem in that.

# INSTALL APACHE PORTABLE RUNTIME FOR JIRA
# TOMCAT: 8.x
# JIRA: 7.12.1
# ----------------------------------------

# run as root: sudo su || su root

# GNU development environment (gcc, make)
apt-get install -y build-essential

# APR 1.2+ development headers (libapr1-dev package)
apt-get install -y libapr1-dev

# OpenSSL 0.9.7+ development headers (libssl-dev package)
apt-get install -y libssl-dev

# JNI headers from Java compatible JDK 1.4+
apt-get install -y openjdk-8-jdk-headless

# Extract jiras tomcat-native
tar -xzvf ${JIRA_INSTALL}/bin/tomcat-native.tar.gz -C /tmp

# Configure Tomcat
cd /tmp/tomcat-native-*/native && \
./configure --with-apr=/usr/bin/apr-1-config --with-java-home=/usr/lib/jvm/java-8-openjdk-amd64 --with-ssl=yes \
&& make && make install

# Libraries will be installed in /usr/local/apr/lib
# Defaults: /lib:/lib64:/usr/lib:/usr/lib64:/usr/java/packages/lib/amd64

# Append additional Java options to the startup script.
tee -a ${JIRA_INSTALL}/bin/setenv.sh > /dev/null << END
#-----------------------------------------------------------------------------------
# Include PATH for Tomcat <--> Apache Portable Runtime Library
#-----------------------------------------------------------------------------------
export JAVA_OPTS="\${JAVA_OPTS} -Djava.library.path=/lib:/lib64:/usr/lib:/usr/lib64:/usr/java/packages/lib/amd64:/usr/local/apr/lib"
END

Lets Encrypt

# Install Letsencrypt Certbot
apt-get install -y certbot

# Allow Port 80 for 
ufw allow 80/tcp

# Create a Certificate
certbot certonly --non-interactive --standalone -d ${DOMAIN} --agree-tos -m ${EMAIL}

# Since JIRA does not like symlinks, we have to copy them elsewhere:
# create custom directory for our SSL certs
mkdir -p /opt/atlassian/ssl

# Copy certificates, while dereferencing the symlink:
cp --dereference \
/etc/letsencrypt/live/${DOMAIN}/cert.pem \
/opt/atlassian/ssl/${DOMAIN}-cert.pem

cp --dereference \
/etc/letsencrypt/live/${DOMAIN}/privkey.pem \
/opt/atlassian/ssl/${DOMAIN}-key.pem

# Add the Letsencrypt chain to jiras keystore
# The password actually *is* "changeit"
/opt/atlassian/jira/jre/bin/keytool -trustcacerts \
    -keystore ${JIRA_INSTALL}/jre/lib/security/cacerts -storepass changeit \
    -noprompt -importcert -file /etc/letsencrypt/live/${DOMAIN}/chain.pem

# (Optional) manually verify:
# ls -l /opt/atlassian/ssl

# Move the server.xml:
mv ${JIRA_INSTALL}/conf/server.xml ${JIRA_INSTALL}/conf/server.xml.orig

# Create a new server.xml
# BEWARE:
# The Variables have to be resolved in this file,
# so copy it "as is" or resolve them manually.
# NOTE:
# If you want a specific SSL port,
# do this here in the <Connector port=SSLPORT> property
cat > /opt/atlassian/jira/conf/server.xml << EOF
<?xml version="1.0" encoding="utf-8"?>
<Server port="8005" shutdown="SHUTDOWN">
    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
    <Service name="Catalina">
    <!-- Custom Jira SSL Connector with APR -->
    <Connector
      protocol="org.apache.coyote.http11.Http11AprProtocol"
      port="443"
      scheme="https"
      secure="true"
      SSLEnabled="true"
      SSLProtocol="TLSv1.2+TLSv1.3"
      maxThreads="200"
      minSpareThreads="25"
      useBodyEncodingForURI="true"
      maxHttpHeaderSize="8192"
      connectionTimeout="20000"
      enableLookups="false"
      disableUploadTimeout="true"
      acceptCount="100"
      clientAuth="false"
      SSLCertificateFile="/opt/atlassian/ssl/${DOMAIN}-cert.pem"
      SSLCertificateKeyFile="/opt/atlassian/ssl/${DOMAIN}-key.pem"
      SSLVerifyClient="optional">
    </Connector>
    <Engine name="Catalina" defaultHost="localhost">
        <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
            <Context path="" docBase="\${catalina.home}/atlassian-jira" reloadable="false" useHttpOnly="true">
                <Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction"
                          factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
                <Manager pathname=""/>
                <JarScanner scanManifest="false"/>
            </Context>
        </Host>
        <Valve className="org.apache.catalina.valves.AccessLogValve"
               pattern="%a %{jira.request.id}r %{jira.request.username}r %t &quot;%m %U%q %H&quot; %s %b %D &quot;%{Referer}i&quot; &quot;%{User-Agent}i&quot; &quot;%{jira.request.assession.id}r&quot;"/>
    </Engine>
    </Service>
</Server>
EOF

# empty the tomcat logs
truncate -s 0 /opt/atlassian/jira/logs/catalina.out

# FINALLY JIRA
systemctl start jira

# Observe the Tomcat output and check for errors:
tail -f /opt/atlassian/jira/logs/catalina.out