System Administrator DevOps Jahia 8.2

How to set up a Jahia cluster?

Question

How to set up a Jahia cluster in production?

Answer

Here are the steps followed to install a Jahia cluster for a production environment. We will also detail the installation of two Apache2 front-ends and give some information if you want to use NGINX instead.

General

Jahia will be installed on a cluster of three nodes:

  1. The first node, the processing, will be used for the contribution (addition of content, etc)
  2. The two other servers will be used for the consultation

Each node has :

  • a Linux distribution
  • 40 GB of hard drive
  • 4 CPUs
  • 16 GB of memory

We will also need two servers for Apache2 and one server for the MySQL database.

A F5 or a failover configuration (like Haproxy) above are recommended but won't be described in this document.

Matrix

The following parameters will be used between "<" an ">" in the next parts of the documentation.

Variable Value Description
DB_NAME jahia Name of the PostgreSQL database
DB_PORT 3306 Port used by MySQL
DB_PASSWORD Change_it_it_s_important Password to access the database
DB_SRV_FQDN mysql01.int.jahia.com Fully qualified domain name of the DB server
DB_USER_NAME jahia User to access the DV
JAHIA_DATASTORE /srv/datastore/jahia Path of the Jahia datastore
JAHIA_HOME /src/jahia Home of Jahia
JAHIA_INSTALLER Jahia-EnterpriseDistribution-8.2.1.0-rbfa9ea8.jar Name of the Jahia installer
JAHIA_ROOT_PASSWORD Needs_to_be_very_very_complexed! Password for the user root
JAHIA_LINUX_USER jahia Linux user used to install and to run Jahia
JAHIA_SRV_01 jahia01 Hostname for the first Jahia server
JAHIA_SRV_02 jahia02 Hostname for the second Jahia server
JAHIA_SRV_03 jahia03 Hostname for the third Jahia server
JAHIA_SRV_FQDN_01 jahia01.int.jahia.com Fully qualified domain name for the first Jahia server
JAHIA_SRV_FQDN_02 jahia02.int.jahia.com Fully qualified domain name for the second Jahia server
JAHIA_SRV_FQDN_03 jahia03.int.jahia.com Fully qualified domain name for the third Jahia server
MAIL_SERVER_URL smtps://email-smtp.eu-west-1.amazonaws.com?username=XXXXXXXXXXXXXXXXXX Mail server url
MAIL_ADDRESS it@jahia.com E-Mail address used to send/receive communications from Jahia
JDK_FILE_NAME graalvm-jdk-17.0.12_linux-x64_bin.tar.gz Archive for the JDK 17
JDK_HOME /usr/lib/jvm/ Home of the JDK
TOMCAT_HOME <JAHIA_HOME>/tomcat Home of Tomcat
WEB_SRV_FQDN_01 web1.int.jahia.com Fully qualified domain name for the first web server
WEB_SRV_FQDN_02 web2.int.jahia.com Fully qualified domain name for the second web server
PUBLIC_CONTRIBUTE_DOMAIN exemple-contribute.jahia.com Public domain to access the contribution/edition
PUBLIC_DOMAIN example.jahia.com Public domain used to access Jahia

Prerequisites

Install

JDK

Jahia runs on a Java platform so it needs a JDK to be installed on the system.

  • Go to the Java Archive page: https://www.oracle.com/java/technologies/javase/graalvm-jdk17-archive-downloads.html
  • Look for the last JDK 17, download the Linux appropriate archive and extract it in the path <JDK_HOME>

 

LibreOffice

 

LibreOffice is being using by Jahia for document conversion.

  • Install it:
    sudo yum install libreoffice

ImageMagick

ImageMagick is being used by Jahia for all operations related to the images (resizing, thumbnails, etc.)

  • Install it:
    sudo yum install ImageMagick

Development tools

Even if you should not need these tools, they might be useful in case of an outage.

  • Install maven/subversion/git:
    sudo yum install maven subversion git

LogRotate

By default, the logs are being rotated but there isn't any mechanism to remove logs older than a specific date. Linux offers a very useful tool to handle the logs.

  • LogRotate should already be installed by default but if it's not the case:
    sudo yum install logrotate

Swappiness

Swappiness is a value between 1 and 100 that controls the priority of the use of the RAM vs the SWAP. In Java applications, the garbage collector might end up using the swap which can slow a lot an application.

  • Check the swappiness
    sudo cat /proc/sys/vm/swappiness
  • If it's below or equal to 10, you don't have to do anything. Otherwise, apply the modifications at runtime:
    sudo sysctl vm.swappiness=10
    sudo swapoff -a
    sudo swapon -a
    
  • Make the modifications persistent by modifying the file /etc/sysctl.conf:
    vm.swappiness = 10

Entropy

Jahia is generating some UUIDs and for that, it needs some randomness and that needs a good entropy. In some virtualized environments, this entropy is not good at all and needs to be improved in order to have good performances.

Please follow this documentation: Jahia and entropy

Jahia Linux user

  • For security reasons, Jahia will need to be installed under a specific user:
    sudo adduser --quiet --disabled-password --disabled-login --home <JAHIA_HOME> --gecos "" <JAHIA_LINUX_USER>

Database connection

  • The nodes of a Jahia cluster are communicating between each other thanks to two TCP ports (By default 7860 and 7870). Please check with your IT team that these flows are authorized.

Database connection

  • The latency against the database needs to be very small, < 0 ms, in order to get good performances:
    ping <DB_SRV_FQDN>
  • Check also that you are able to connect with the DB client and the credentials that will be used for Jahia. This user needs to be able to create the necessary tables and to insert/update/delete their content.
  • In case you're using a dedicated schema, please be sure that it's the default schema or you will have to customize the JDBC url.

SMTP connection

  • Check that you're able to send mail with the SMTP server you want to use (SMTP relay, SMTP service, etc) from each Jahia server

Shared folder

The Jahia cluster will need a shared folder in order to store the datastore. In order to prevent a SPOF, we advise to check that the related hardware and software have some redundancy. The easiest way is to give full rights for this user on this table.

  • Check that the user jahia has the right to create a file in the datastore
    sudo -u <JAHIA_LINUX_USER> touch <JAHIA_DATASTORE>
  • Do not forget to remove this file

Internet connection

Check that you have access to Internet

Jahia cluster

Install

  • First we are going to install the processing node
  • Become the user jahia:
    sudo -i -u <JAHIA_LINUX_USER>
  • Retrieve the last minor version of Jahia you're using from the page Jahia. Why this version? Because it might contain many bug fixes.
  • To install it:
    java -jar <JAHIA_INSTALLER> -console
  • Then follow these actions:
    1. Accept the license
    2. Enter the path of Jahia: <JAHIA_HOME>
    3. Choose the Custom install (advanced)
    4. By default, keep the installation of Tomcat
    5. By default, use a standalone DBMS (DataBase Management System)
    6. Choose MySQL
    7. Download the MySQL JDBC driver, select it, accept the T&Cs
    8.  
    9. Enter the Database URL related to your environment: jdbc:mysql://<DB_SRV_FQDN>:<DB_PORT>/<DB_NAME>?characterEncoding=UTF-8
    10. Enter the Database username: <DB_USER_NAME>
    11. Enter the Database password: <DB_PASSWORD>
    12. The datastore will be a shared folder so do not store binary data in the database
    13. By default, create the required database tables
    14. By default, keep the context name empty
    15. By default, keep the SSH console port set to 8101
    16. By default, keep the HTTP connector port set to 8080
    17. By default, keep the HTTPS redirect port set to 8443
    18. By default, keep the AJP connector port set to 8009
    19. By defaut, keep the Server shutdown port set to 8005
    20. Set the jvmRoute to the hostname, it will ease the maintenance/debugging later: <JAHIA_SRV_01>
    21. Set the Maximum heap size in MB to 8192
    22. By default, keep the Maximum PermGen size in MB to 384
    23. There is no need at this level to add any additional option to the JVM
    24. Set the operating mode to Production
    25. If not needed, do not configure an LDAP provider
    26. Activate the cluster mode by selecting the option Provide cluster configuration for this server
    27. As this is the first server of the cluster, it has to be a background job processing server. A cluster can only have one processing server otherwise it can lead to a full corruption of the data.
    28. If your server has multiple network interfaces, enter the IP that will be used to communicate with the other servers
    29. By default, keep the Port number set to 7870
    30. By default, keep the Hazelcast Port set to 7860
    31. For the Current node server ID, set it to the hostname, it will the maintenance/debugging later
    32. Set the path to the datastore: <JAHIA_DATASTORE>
    33. Enter the password for the root user (it needs to be complex): <JAHIA_ROOT_PASSWORD>
    34. Confirm it
    35. The First name, Last name and E-mail are optional, put what you want
    36. Choose the Preferred language: 1 for English, 3 for French
    37. Set the Mail server to <MAIL_SERVER_URL>
    38. Set the Mail administrator to the team that will handle the servers: <MAIL_ADDRESS>
    39. Set the Mail from - sender to the team that will handle Jahia: <MAIL_ADDRESS>
    40. Validate the settings to be sure that Jahia will be correctly installed
    41. If LibreOffice is installed, its path should be automatically set. If it's not the case, it should be /usr/lib/libreoffice
    42. The binaries of ImageMagick should be installed by default in /usr/bin
    43. Choose the default language: 41 for English, 60 for French
    44. Keep the development license provided by default
    45. No need to add a path to a folder with additional modules to be deployed
    46. By default, keep the Runtime data path: <JAHIA_HOME>/digital-factory-data
    47. By default, keep the Configuration path: <JAHIA_HOME>/digital-factory-config
    48. Installation should be done and here is what you should have in the console:
      [ Starting to unpack ]
      [ Processing package: Jahia + Jahia Core Content Platform (1/2) ]
      [ Processing package: Add Apache Tomcat - generate a complete installation folder (2/2) ]
      [ Unpacking finished ]
      Install was successful
      application installed on /srv/jahia
      [ Console installation done ]

Logs rotation

  • Create the file <TOMCAT_HOME>/webapps/ROOT/WEB-INF/etc/config/log4j2.xml with the following content:
<?xml version="1.0" encoding="UTF-8"?>

<Configuration
    packages="org.jahia.bin.errors,org.jahia.services.notification"
    monitorInterval="30">

    <Appenders>
        <File name="JahiaLog"
            fileName="${sys:jahia.log.dir}/jahia.log">
            <PatternLayout
                pattern="%d: %-5p [%t] %c: %encode{%.-500m}{CRLF}%n%sxThrowable"/>
            <ThresholdFilter level="DEBUG"/>
        </File>

        <File name="GWTRPC"
            fileName="${sys:jahia.log.dir}/gwtrpc.log">
            <PatternLayout
                pattern="%d: %-5p [%t] %c: %encode{%m}{CRLF}%n%sxThrowable"/>
            <ThresholdFilter level="DEBUG"/>
        </File>

        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                pattern="%d: %-5p [%c{1}] - %encode{%.-500m}{CRLF}%n%sxThrowable"/>
            <ThresholdFilter level="DEBUG"/>
        </Console>

        <File name="ProfilerLog"
            fileName="${sys:jahia.log.dir}/jahia_profiler.log">
            <PatternLayout
                pattern="%d %encode{%.-500m}{CRLF}%n"/>
            <ThresholdFilter level="DEBUG"
                onMatch="ACCEPT" onMismatch="DENY"/>
        </File>
        
        <File name="JahiaAccessLog"
            fileName="${sys:jahia.log.dir}/jahia_access.log">
            <PatternLayout pattern="%d %encode{%.-500m}{CRLF}%n"/>
            <ThresholdFilter level="TRACE"
                onMatch="ACCEPT" onMismatch="DENY"/>
        </File>

        <ExceptionAppender name="ExceptionLogging"/>

        <CamelAppender name="CamelNotification"
            targetUri="direct:logs">
            <PatternLayout pattern="%d %encode{%.-500m}{CRLF}"/>
            <ThresholdFilter level="TRACE"
                onMatch="ACCEPT" onMismatch="DENY"/>
        </CamelAppender>
    </Appenders>

    <Loggers>
        <Logger name="REQUEST">
            <AppenderRef ref="JahiaLog"/>
        </Logger>

        <Logger name="SysOut">
            <AppenderRef ref="Console"/>
        </Logger>

        <Logger name="accessLogger" additivity="false">
            <AppenderRef ref="RollingJahiaAccessLog"/>
            <AppenderRef ref="CamelNotification"/>
        </Logger>

        <Logger name="loggingService" additivity="false"
            level="trace">
            <AppenderRef ref="RollingJahiaAccessLog"/>
            <AppenderRef ref="CamelNotification"/>
        </Logger>

        <Logger name="profilerLoggingService" additivity="false"
            level="info">
            <AppenderRef ref="ProfilerLog"/>
        </Logger>

        <Logger name="org.jahia.ajax.gwt.commons.server.GWTController" level="info">
            <AppenderRef ref="GWTRPC"/>
        </Logger>

        <Logger name="org.apache.commons" level="error"/>
        <Logger
            name="org.apache.jackrabbit.spi.commons.nodetype.constraint.ValueConstraint"
            level="error"/>
        <Logger name="net.htmlparser.jericho" level="warn"/>
        <Logger name="net.sf.ehcache" level="error"/>
        <Logger name="net.sf.ehcache.pool.impl" level="warn"/>
        <Logger name="org.quartz" level="info"/>
        <Logger name="org.jahia.bin.errors" level="warn"/>
        <Logger name="org.jahia.services.render.filter.cache"
            level="info"/>
        <Logger
            name="org.jahia.services.textextraction.TextExtractionService"
            level="info"/>
        <Logger name="org.jgroups.protocols.pbcast.GMS"
            level="debug"/>
        <Logger name="org.hibernate" level="warn"/>
        <Logger name="org.hibernate.cfg.Environment" level="info"/>
        <Logger name="org.hibernate.orm.deprecation" additivity="false" level="WARN">
            <RegexFilter regex=".*HHH90000014|HHH90000016.*" onMatch="DENY" onMismatch="NEUTRAL"/>
        </Logger>
        <Logger name="org.springframework" level="error"/>
        <Logger
            name="org.jahia.bundles.extender.jahiamodules.Activator"
            level="debug"/>
        <Logger name="org.apache.karaf.cellar" level="debug"/>
        <Logger
            name="org.jahia.bundles.clustering.impl.ClusterBundleEventHandler"
            level="debug"/>
        <!--    Logger AetherBasedResolver set in ERROR as the WARN is displaying some credentials    -->
        <Logger
            name="org.ops4j.pax.url.mvn.internal.AetherBasedResolver"
            level="ERROR"/>
        <Logger name="org.atmosphere.handler" level="ERROR"/>
        <Logger name="org.apache.poi" level="WARN" />
        <Root level="info">
            <AppenderRef ref="JahiaLog"/>
            <AppenderRef ref="Console"/>
            <AppenderRef ref="ExceptionLogging"/>
        </Root>
    </Loggers>
</Configuration>
  • Modify the file <TOMCAT_HOME>/conf/server.xml in order to have the following content for the ValveAccessLog:
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="access-log" suffix=".txt" rotatable="false"
               pattern="%{org.apache.catalina.AccessLog.RemoteAddr}r %l %u %t &quot;%r&quot; %s %b" />
  • Below the previous modification, add also this content for the RemoteIpValve:
        <Valve className="org.apache.catalina.valves.RemoteIpValve"
               remoteIpHeader="x-forwarded-for"
               proxiesHeader="x-forwarded-by"
               protocolHeader="x-forwarded-proto" 
               protocolHeaderHttpsValue="https"/>
  • Modify the file <TOMCAT_HOME>/conf/logging.properties in order to have the following content:
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

handlers = java.util.logging.ConsoleHandler

.handlers = java.util.logging.ConsoleHandler

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

1catalina.org.apache.juli.AsyncFileHandler.level = ALL
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.
1catalina.org.apache.juli.AsyncFileHandler.maxDays = 90
1catalina.org.apache.juli.AsyncFileHandler.encoding = UTF-8

2localhost.org.apache.juli.AsyncFileHandler.level = ALL
2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.
2localhost.org.apache.juli.AsyncFileHandler.maxDays = 90
2localhost.org.apache.juli.AsyncFileHandler.encoding = UTF-8

3manager.org.apache.juli.AsyncFileHandler.level = ALL
3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.AsyncFileHandler.prefix = manager.
3manager.org.apache.juli.AsyncFileHandler.maxDays = 90
3manager.org.apache.juli.AsyncFileHandler.encoding = UTF-8

4host-manager.org.apache.juli.AsyncFileHandler.level = ALL
4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.
4host-manager.org.apache.juli.AsyncFileHandler.maxDays = 90
4host-manager.org.apache.juli.AsyncFileHandler.encoding = UTF-8

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8


############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler

# For example, set the org.apache.catalina.util.LifecycleBase logger to log
# each component that extends LifecycleBase changing state:
#org.apache.catalina.util.LifecycleBase.level = FINE

# To see debug messages for HTTP/2 handling, uncomment the following line:
#org.apache.coyote.http2.level = FINE

# To see debug messages for WebSocket handling, uncomment the following line:
#org.apache.tomcat.websocket.level = FINE
  • Create the file /etc/logrotate.d/jahia_logrotate with the following content:
<TOMCAT_HOME>/logs/access-log.txt {
        missingok
        su jahia jahia
        compress
        copytruncate
        rotate 14
        daily
        maxsize 500M
        dateext
        dateformat _%Y-%m-%d
}

<TOMCAT_HOME>/logs/catalina.out {
        missingok
        su jahia jahia
        compress
        copytruncate
        rotate 14
        daily
        maxsize 500M
        dateext
        dateformat _%Y-%m-%d
}

<TOMCAT_HOME>/logs/jahia_access.log {
        missingok
        su jahia jahia
        compress
        copytruncate
        rotate 14
        daily
        maxsize 500M
        dateext
        dateformat _%Y-%m-%d
}

<TOMCAT_HOME>/logs/jahia.log {
        missingok
        su jahia jahia
        compress
        copytruncate
        rotate 14
        daily
        maxsize 500M
        dateext
        dateformat _%Y-%m-%d
}

<TOMCAT_HOME>/logs/jahia_profiler.log {
        missingok
        su jahia jahia
        compress
        copytruncate
        rotate 14
        daily
        maxsize 500M
        dateext
        dateformat _%Y-%m-%d
}
  • Check that the previous file has the correct permission 644

Jahia as a service

  • Create the file /etc/systemd/system/jahia.service with the following content:
[Unit]
Description=Jahia
After=network.target

[Service]
Type=simple

Environment=JAVA_HOME=<JDK_HOME><JDK_FILE_NAME>
Environment=JRE_HOME=<JDK_HOME><JDK_FILE_NAME>
Environment=CATALINA_PID=<TOMCAT_HOME>/temp/tomcat.pid
Environment=CATALINA_HOME=<TOMCAT_HOME>
Environment=CATALINA_BASE=<TOMCAT_HOME>
Environment="CATALINA_OPTS=-server"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/urandom"

ExecStart=<TOMCAT_HOME>/bin/catalina.sh run
ExecStop=/bin/kill -15 $MAINPID
WorkingDirectory=<JAHIA_HOME>

User=jahia
Group=jahia
RestartSec=10
Restart=on-failure
SuccessExitStatus=143
TimeoutSec=300

[Install]
WantedBy=multi-user.target
  • Check that the previous file has the correct permission 755
  • Enable the service with the following command:
systemctl enable jahia

Start and check Jahia

  • Start Jahia thanks to its service:
    systemctl start jahia
  • Display the stdout logs:
    journalctl -f -u jahia
  • Wait for Jahia to be completely started, you should see that in the logs:
2025-05-12 15:37:57,806: INFO  [EndInit] - --------------------------------------------------------------------------------------------------
2025-05-12 15:37:57,806: INFO  [EndInit] -   P R O D U C T I O N   M O D E   A C T I V E
2025-05-12 15:37:57,806: INFO  [EndInit] - --------------------------------------------------------------------------------------------------
2025-05-12 15:37:57,806: INFO  [EndInit] -   Modules:
2025-05-12 15:37:57,806: INFO  [EndInit] -       Started: 47
2025-05-12 15:37:57,807: INFO  [EndInit] - --------------------------------------------------------------------------------------------------
2025-05-12 15:37:57,807: INFO  [EndInit] -   Jahia 8.2.1.0 is now ready. Initialization completed in 74 seconds
2025-05-12 15:37:57,807: INFO  [EndInit] - --------------------------------------------------------------------------------------------------
  • If you don't have all the default modules started, it might be the consequence of a problem. If that's the case, please contact our support at https://support.jahia.com if you have a subscription.
  • Now it's time to check that we have access to the administration and to the tools:
    • Go to http://<JAHIA_SRV_01>:8080/administration and login
    • Go to http://<JAHIA_SRV_01>:8080/tools and login
      • Open the cluster view and check that everything seems to be correct: you should see in the tables the processing server with the correct IP and ports. If you have a doubt, please contact our support at https://support.jahia.com if you have a subscription.

Installation of the two other nodes

  • Stop Jahia on the processing node and make a backup thanks to an archive. The archive is used in order to preserve the attributes of the files (permissions and timestamps).
  • Transfer the archive to the two other servers
  • On each server:
    • Check that the prerequisites are fine
    • As the user jahia, extract the archive in the same folder as the processing node
    • Modify
      • the file <JAHIA_HOME>/digital-factory-config/jahia/jahia.node.properties
        • Property cluster.tcp.bindAddress has to be set to the IP of the server if you have multiple network interfaces
        • Property processingServer has to be uncommented and set to false
        • Property cluster.node.serverId has to be set to the hostname of the server
      • the file <TOMCAT_HOME>/conf/server.xml
        • Property jvmRoute has to be set to the hostname
  • Put into place the log rotation as before
  • Put into place the service as before
  • Start Jahia thanks to its service on each node then check the same things as before

Front-ends

Apache2

The front-end will proxy the Jahia nodes thanks to the HTTP protocol. Depending on where SSL is being handled (Failover and/or Apache2 and/or Tomcat), the next steps might need some changes.

  • Here is an example of the configuration for an Apache2 virtual host, that should be in /etc/httpd/sites-available/<PUBLIC_DOMAIN>.conf:
    <VirtualHost *:80>
            ServerName <PUBLIC_DOMAIN>
            DocumentRoot /var/www/vhosts/<PUBLIC_DOMAIN>/html/
    
            <Directory /var/www/vhosts/<PUBLIC_DOMAIN>/html>
                    Options -Indexes
            </Directory>
    
            CustomLog /var/log/apache2/access-<PUBLIC_DOMAIN>.log combined
            ErrorLog /var/log/apache2/error-<PUBLIC_DOMAIN>.log
    
            RewriteEngine On
            RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
            RewriteRule .* - [F]
    
            RewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
    </VirtualHost>
    
    <VirtualHost *:80>
            ServerName <PUBLIC_CONTRIBUTE_DOMAIN>
            DocumentRoot /var/www/vhosts/<PUBLIC_CONTRIBUTE_DOMAIN>/html/
    
            <Directory /var/www/vhosts/<PUBLIC_CONTRIBUTE_DOMAIN>/html>
                    Options -Indexes
            </Directory>
    
            CustomLog /var/log/apache2/access-<PUBLIC_CONTRIBUTE_DOMAIN>.log combined
            ErrorLog /var/log/apache2/error-<PUBLIC_CONTRIBUTE_DOMAIN>.log
    
            RewriteEngine On
            RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
            RewriteRule .* - [F]
    
            RewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
    </VirtualHost>
    
    <VirtualHost *:443>
            ServerName <PUBLIC_DOMAIN>
            DocumentRoot /var/www/vhosts/<PUBLIC_DOMAIN>/html/
    
            <Directory /var/www/vhosts/<PUBLIC_DOMAIN>/html>
                    Options -Indexes
            </Directory>
    
            CustomLog /var/log/apache2/access-<PUBLIC_DOMAIN>.log combined
            ErrorLog /var/log/apache2/error-<PUBLIC_DOMAIN>.log
    
            ProxyPreserveHost On
            ProxyRequests Off
    
            ProxyPass /modules/graphqlws balancer://jahia_cluster/modules/graphqlws
            ProxyPassReverse /modules/graphqlws balancer://jahia_cluster/modules/graphqlws
            <Proxy balancer://jahia_cluster_ws>
                    BalancerMember ws://<JAHIA_SRV_FQDN_02>:8080 route=<JAHIA_SRV_02> connectiontimeout=20 timeout=300 ttl=120
                    BalancerMember ws://<JAHIA_SRV_FQDN_03>:8080 route=<JAHIA_SRV_03> connectiontimeout=20 timeout=300 ttl=120                
                    ProxySet lbmethod=bytraffic stickysession=JSESSIONID|jsessionid
            </Proxy>
    
    
            ProxyPass / balancer://jahia_cluster/
            ProxyPassReverse / balancer://jahia_cluster/
            <Proxy balancer://jahia_cluster>
                    BalancerMember http://<JAHIA_SRV_FQDN_02>:8080 route=<JAHIA_SRV_02> connectiontimeout=20 timeout=300 ttl=120
                    BalancerMember http://<JAHIA_SRV_FQDN_03>:8080 route=<JAHIA_SRV_03> connectiontimeout=20 timeout=300 ttl=120                
                    ProxySet lbmethod=bytraffic stickysession=JSESSIONID|jsessionid
            </Proxy>
    
            RemoteIPHeader X-Forwarded-For
            Include ssl-common.conf
    
            # BACKLOG-5641
            Header set X-XSS-Protection "1; mode=block"
            Header set X-Content-Type-Options nosniff
            # BACKLOG-5641
            Header set X-Frame-Options: "sameorigin"
    
            Include dx-security.conf
    
            # Redirect to the back-end when /jahia is present in the URI
            RewriteEngine On
            RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
            RewriteRule .* - [F]
            RewriteRule ^(/jahia/.*)$ https://<PUBLIC_CONTRIBUTE_DOMAIN>%{REQUEST_URI} [R=301,L]
    </VirtualHost>
    
    <VirtualHost *:443>
            ServerName <PUBLIC_CONTRIBUTE_DOMAIN>
            DocumentRoot /var/www/vhosts/<PUBLIC_CONTRIBUTE_DOMAIN>/html/
    
            <Directory /var/www/vhosts/<PUBLIC_CONTRIBUTE_DOMAIN>/html>
                    Options -Indexes
            </Directory>
    
            CustomLog /var/log/apache2/access-<PUBLIC_CONTRIBUTE_DOMAIN>.log combined
            ErrorLog /var/log/apache2/error-<PUBLIC_CONTRIBUTE_DOMAIN>.log
    
            ProxyPreserveHost On
            ProxyRequests Off
    
            ProxyPass /modules/graphqlws balancer://jahia_cluster/modules/graphqlws
            ProxyPassReverse /modules/graphqlws balancer://jahia_cluster/modules/graphqlws
            <Proxy balancer://jahia_cluster_ws>
                    BalancerMember ws://<JAHIA_SRV_FQDN_01>:8080 route=<JAHIA_SRV_01> connectiontimeout=20 timeout=300 ttl=120
                    ProxySet lbmethod=bytraffic stickysession=JSESSIONID|jsessionid
            </Proxy>
    
            ProxyPass / balancer://jahia_cluster/
            ProxyPassReverse / balancer://jahia_cluster/
            <Proxy balancer://jahia_cluster>
                    BalancerMember http://<JAHIA_SRV_FQDN_01>:8080 route=<JAHIA_SRV_01> connectiontimeout=20 timeout=300 ttl=120
                    ProxySet lbmethod=bytraffic stickysession=JSESSIONID|jsessionid
            </Proxy>
    
            RemoteIPHeader X-Forwarded-For
            RequestHeader set X-Forwarded-Proto "https"
    
            Include ssl-common.conf
            Header set X-Robots-Tag "noindex, nofollow"
    
            # BACKLOG-5641
            Header set X-XSS-Protection "1; mode=block"
            Header set X-Content-Type-Options nosniff
            # BACKLOG-5641
            Header set X-Frame-Options: "sameorigin"
    
            Include dx-security.conf
    </VirtualHost>

And here are some files included in this virtual host:

  • dx-security.conf: restrict the access to sensitive URLs (administration, contributing, edition, tools, etc) to some trusted IPs
    # secure admin/contrib URLs from outside our network
    Define allowed_ip "LIST_OF_SECURED_IPS"
    <LocationMatch "^/(start|cms\/admin|welcome\/adminmode|cms\/edit|cms\/contribute|cms\/studio|tools|modules\/tools|repository|server)">
        Require ip ${allowed_ip}
    </LocationMatch>
    
  • ssl-common.conf: only few directives in order to activate the SSL.
    SSLEngine on
    SSLCertificateKeyFile /etc/apache2/ssl/privkey.pem
    SSLCertificateFile /etc/apache2/ssl/fullchain.pem
    RequestHeader set X-Forwarded-Proto "https"
    
  • Once your configuration is ready, you have to activate the site with this command:
    a2ensite <PUBLIC_DOMAIN>
  • Depending on your configuration, you will need to activate the Apache2 modules.
    • Here is a list according to our example:
      • headers_module
      • lbmethod_bytraffic_module
      • mpm_prefork_module
      • proxy_module
      • proxy_balancer_module
      • proxy_http_module
      • remoteip_module
      • reqtimeout_module
      • rewrite_module
      • ssl_module
      • status_module
    • Activate the related module thanks to this command:
      a2enmod <MODULE_NAME>
  • Do not forget to run this command before reloading/restarting the Apache2 service to test it:
    apachectl configtest
    • If you're getting an error like Invalid command ‘XXXXX’, perhaps misspelled or defined by a module, it means that an HTTP module needs to be activated.  To correct that, you have to:
      • Go to the Apache2 documentation and look for module who handles this command/directive, we will call it <MODULE_NAME>
      • Activate the related module thanks to this command:
        a2enmod <MODULE_NAME>
      • Test again your configuration
  • If it's ok, reload the service:
    systemctl reload httpd
  • Once your Apache2 is set up, you can check a few things:
    • Connect to Jahia and check in the logs that the client IP is correct. It should be the one of the client and not the one from the web server or any network element at an upper level. To get the expected IP of the client, you can to the website What is my public IP
    • In the administration mode of Jahia, create a sample website with the server name set to the same public domain used in the virtual host and publish the website. Try to connect to it and you should have a URL like https://<PUBLIC_DOMAIN>.home.html and not something like https://<PUBLIC_DOMAIN>/cms/render/live/en/sites/<SITE_KEY>/home.html
    • Check the quality of your SSL thanks to this website: https://www.ssllabs.com/ssltest/

Alternative: NGINX

Here is an example of configuration for Nginx, that should be in /etc/nginx/sites-enabled/<PUBLIC_DOMAIN>:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
    
upstream jahia_browsing {
    ip_hash;

    server <JAHIA_SRV_FQDN_02>:8080;
    server <JAHIA_SRV_FQDN_03>:8080;
}

upstream jahia_contributing {
    ip_hash;

    server <JAHIA_SRV_FQDN_01>:8080;
}


server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}
    
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate     ssl/fullchain.pem;
    ssl_certificate_key ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    
    root /var/www/html;
    
    server_name <PUBLIC_DOMAIN>;
    
    location / {
        proxy_set_header Host $http_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-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://jahia_browsing;
    }
    
    location /modules/graphqlws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $http_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-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://jahia_browsing;
    }
    
     rewrite ^(/jahia/.+)$ https://<PUBLIC_CONTRIBUTE_DOMAIN>$1 permanent;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate     ssl/fullchain.pem;
    ssl_certificate_key ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    
    root /var/www/html;
    
    server_name <PUBLIC_CONTRIBUTE_DOMAIN>;
    
    location ~ ^/(findUser/|findUsersAndGroups|findUsersAndGroupsInAcl|start|cms\/admin|welcome\/adminmode|cms\/edit|cms\/contribute|cms\/studio|tools|modules\/tools|repository|server|jahia) {
        allow X.X.X.X;
        deny all;
        proxy_set_header Host $http_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-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://jahia_contributing;
    }
        
    location / {
        proxy_set_header Host $http_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-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://jahia_contributing;
    }
    
    location /modules/graphqlws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $http_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-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://jahia_contributing;
    }
}

Please note that here, we use the directive ip_hash for the sticky sessions. It's because we have the open-source version of NGINX. If you have the enterprise version, you should be able to use the directive sticky.