wms - Web Map Service

Once installation and configuration are complete, you can start the Web Map Service application (provided you have forecast data to visualise). The file “mswms” is an executable Python script starting up a Flask HTTP server with the WMS WSGI module. A short description of how to start the program is given by the –help option. The file “wms.wsgi” is intended to be used with an Apache web server installation.

We have a single method to use data for ECMWF, CLaMS, GWFC, EMAC, METEOSAT implemented. The data have to use for their parameters the CF attribute standard_name. A new method should be able to deal with any CF conforming file following a couple of simple additional requirements.

Per configuration you could register horizontal (register_horizontal_layers) or vertical layers (register_vertical_layers), give a basemap table for EPSG mapping (epsg_to_mpl_basemap_table) and at all how to access the data.

A few notes:

  • The Flask WMS currently cannot run multithreaded (Apache does support multiple processes). This is due to that a single instance of the WSGI application handler class MSS_WMSResponse can create only one plot at a time (otherwise you get messed up plots when simultaneous requests occur). In the current implementation, only a single instance is passed to Flask (to do all the initialisation work only once. To extend the software to handle simultaneous requests would probably involve creating a “factory” of MSS_WMSResponse instances.. If you want to do this, check if/how Flask handles “worker” factories.
  • Creating the capabilities document can take very long (> 1 min) if the forecast data files have to be read for the first time (the WMS program opens all files and tries to determine the available data and elevation ranges). A GetCapabilities request should return a document within a few seconds.
  • A typical bottleneck for plot generation is when the forecast data files are located on a different computer than the WMS server. In this case, large amounts of data have to be transferred over the network. Hence, when possible, try to make sure the WMS runs on the same computer on which the input data files are hosted.

Configuration file of the wms server

Configuration for the Mission Support System Web Map Service (wms).

In this module the data organisation structure of the available forecast data is described. The class NWPDataAccess is subclassed for each data type in the system and provides methods to determine which file needs to be accessed for a given variable and time. The classes also provide methods to query the available initialisation times for a given variable, and the available valid times for a variable and a given initialisation time. As the latter methods need to open the NetCDF data files to determine the contained time values, a caching system is used to avoid re-opening already searched files.

Replace the name INSTANCE in the following examples by your service name.

The configuration file have to become added to the /home/mss/INSTANCE/config directory


# -*- coding: utf-8 -*-


    Configuration module for programs accessing data on the MSS server.

    This file is part of mss.

    :copyright: 2008-2014 Deutsches Zentrum fuer Luft- und Raumfahrt e.V.
    :copyright: 2011-2014 Marc Rautenhaus
    :copyright: Copyright 2016-2019 by the mss team, see AUTHORS.
    :license: APACHE-2.0, see LICENSE for details.

    Licensed 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


    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.

import os
import sys

# on a productions system you may want to limit the amout of tracebacks to 0
# sys.tracebacklimit = 0

# Configuration of Python's code search path
# If you already have set up the PYTHONPATH environment variable for the
# stuff you see below, you don't need to do a1) and a2).

# a1) Path of the directory where the mss code package is located.
# sys.path.insert(0, '/home/mss/INSTANCE/miniconda3/lib/python3.X/site-packages')

# a2) Path of the directory where mss_wms_settings.py is located
MSSCONFIGPATH = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
sys.path.insert(0, MSSCONFIGPATH)

import mslib.mswms.dataaccess
from mslib.mswms import mpl_hsec_styles
from mslib.mswms import mpl_vsec_styles
import mslib.mswms

# SETTINGS                                  ###

# Paths to data directories. Process callables (the functions that are
# executed by the dispatcher system) can access these paths, hence
# simply define the key/value pairs that are required.
datapath = {
    "ecmwf": "/path/to/data/mss/grid/ecmwf/netcdf",
    "emac": "/path/to/data/mss/grid/emac/netcdf",
    "meteosat": "/path/to/data/mss/grid/meteosat/netcdf",
    "camsglobal": "/path/to/data/mss/grid/camsglobal/netcdf",

# Objects that let the user query the filename in which a particular
# variable can be found. Objects are instances of subclasses of NWPDataAccess,
# which provides the methods fc_filename() and full_fc_path().

data = {
    "ecmwf_NH_LL05": mslib.mswms.dataaccess.DefaultDataAccess(datapath["ecmwf"], "NH_LL05"),
    #    "ecmwf_EUR_LL015": mslib.mswms.dataaccess.DefaultDataAccess(datapath["ecmwf"], "EUR_LL015"),
    #    "meteosat_EUR_LL05": mslib.mswms.dataaccess.DefaultDataAccess(datapath["meteosat"], "EUR_LL05"),
    #    "emac_GLOBAL_LL1125": mslib.mswms.dataaccess.DefaultDataAccess(datapath["emac"]),
    #    "CAMSglb": mslib.mswms.dataaccess.DefaultDataAccess(datapath["camsglobal"]),


# HTTP Authentication                               ###

# If you require basic HTTP authentication, set the following variable
# to True. Add usernames in the list "allowed:users". Note that the
# passwords are not specified in plain text but by their md5 digest.
enable_basic_http_authentication = False

# Basemap cache                                     ###

# Plotting coastlines on horizontal cross-sections requires usually the parsing
# of the corresponding databases for each plot.
# A simple caching feature allows to reuse this data from previous plots using
# the same bounding box and projection parameters, dramatically speeding up
# the plotting. 'basemap_cache_size' determines hows many sets of coastlines shall
# be stored in memory and 'basemap_request_size' determines the length of history
# used to determine, which data shall be purged first if the cache exceeds its
# maximum size.
basemap_use_cache = False
basemap_request_size = 200
basemap_cache_size = 20

# Registration of horizontal layers.                     ###

# The following list contains tuples of the format (instance of
# visualisation classes, data set). The visualisation classes are
# defined in mpl_hsec.py and mpl_hsec_styles.py. Add only instances of
# visualisation products for which data files are available. The data
# sets must be defined in mss_config.py. The WMS will only offer
# products registered here.

register_horizontal_layers = [
    # ECMWF standard surface level products.
    (mpl_hsec_styles.MPLBasemapHorizontalSectionStyle, ["ecmwf_NH_LL05"]),
    (mpl_hsec_styles.HS_CloudsStyle_01, ["ecmwf_EUR_LL015", "ecmwf_NH_LL05"]),
    (mpl_hsec_styles.HS_MSLPStyle_01, ["ecmwf_EUR_LL015", "ecmwf_NH_LL05"]),
    (mpl_hsec_styles.HS_SEAStyle_01, ["ecmwf_NH_LL05"]),
    (mpl_hsec_styles.HS_SeaIceStyle_01, ["ecmwf_NH_LL05"]),
    (mpl_hsec_styles.HS_VIProbWCB_Style_01, ["ecmwf_EUR_LL015"]),

    # ECMWF standard pressure level products.
    (mpl_hsec_styles.HS_TemperatureStyle_PL_01, ["ecmwf_EUR_LL015"]),
    (mpl_hsec_styles.HS_GeopotentialWindStyle_PL, ["ecmwf_EUR_LL015"]),
    (mpl_hsec_styles.HS_RelativeHumidityStyle_PL_01, ["ecmwf_EUR_LL015"]),
    (mpl_hsec_styles.HS_EQPTStyle_PL_01, ["ecmwf_EUR_LL015"]),
    (mpl_hsec_styles.HS_WStyle_PL_01, ["ecmwf_EUR_LL015"]),
    (mpl_hsec_styles.HS_DivStyle_PL_01, ["ecmwf_EUR_LL015"]),

    # ECMWF standard model level products.
    (mpl_hsec_styles.HS_TemperatureStyle_ML_01, ["ecmwf_EUR_LL015"]),

    # ECMWF standard potential vorticity products.
    (mpl_hsec_styles.HS_PVTropoStyle_PV_01, ["ecmwf_EUR_LL015"]),

    # EMAC layers.
    # (mpl_hsec_styles.HS_EMAC_TracerStyle_ML_01, ["emac_GLOBAL_LL1125"]),
    # (mpl_hsec_styles.HS_EMAC_TracerStyle_SFC_01, ["emac_GLOBAL_LL1125"]),

    # Meteosat products.
    (mpl_hsec_styles.HS_Meteosat_BT108_01, ["meteosat_EUR_LL05"]),

    # MSS-Chem chemistry forecasts
    # The MSS-Chem project (http://mss-chem.rtfd.io) provides an easy way to
    # download and prepare chemical weather forecasts from a range of different
    # CTMs.  The plot style classes are called `HS_MSSChemStyle_XL_YYY_zzzzz`
    # and `VS_MSSChemStyle_XL_YYY_zzzzz` for horizontal and vertical sections,
    # i.e., "maps" and "cross-sections", respectively.
    # In the class name, X determines the type of vertical layering of the model
    # data, and can be one of
    # - "P" for model data defined on pressure levels
    # - "M" for model data defined on model levels
    # - "A" for model data defined on altitude levels.
    #   CAUTION: In the latter case, the vertical axis in vertical section plots
    #   (VS_MSSChemStyle_AL_YYY_zzzz) is only approximate, as the air pressure
    #   can only be estimated using the barometric formula.
    # YYY stands for the chemical species to be displayed (in UPPER CASE), e.g.,
    # "NO2".
    # zzzzz determines the type of quantity, and can be one of
    # - mfrac -> mass fraction, kg/kg
    # - mconc -> mass concentration, kg/m3
    # - nfrac -> mole fraction, mol/mol
    # - nconc -> mole_concentration, mol/m3
    # Putting this together, the following class provides a horizontal ("HS")
    # section of model-level ("ML") mass fractions ("mfrac") of nitrogen dioxide
    # ("NO2"):

    # (mpl_hsec_styles.HS_MSSChemStyle_ML_NO2_mfrac, ["CAMSglb"]),

# Registration of vertical layers.                       ###

# The same as above, but for vertical cross-sections.

register_vertical_layers = [
    # ECMWF standard vertical section styles.
    (mpl_vsec_styles.VS_CloudsStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_HorizontalVelocityStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_PotentialVorticityStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_ProbabilityOfWCBStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_VerticalVelocityStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_RelativeHumdityStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_SpecificHumdityStyle_01, ["ecmwf_EUR_LL015"]),
    (mpl_vsec_styles.VS_TemperatureStyle_01, ["ecmwf_EUR_LL015"]),

    # EMAC layers.
    # (mpl_vsec_styles.VS_EMACEyja_Style_01, ["emac_GLOBAL_LL1125"]),

    # MSS-Chem chemistry forecasts
    # (mpl_vsec_styles.VS_MSSChemStyle_ML_NO2_mfrac, ["CAMSglb"]),

# Server settings.                                ###

use_threadpool = False

# xml_template directory is a sub directory of mswms

base_dir = os.path.abspath(os.path.dirname(mslib.mswms.__file__))
xml_template_location = os.path.join(base_dir, "xml_templates")

# get_capabilities.pt
service_name = "OGC:WMS"
service_title = "Mission Support System Web Map Service"
service_abstract = "Your Abstract"
service_contact_person = "Your Name"
service_contact_organisation = "Your Organization"
service_contact_position = "Your Position"
service_address_type = "postal"
service_address = "street"
service_city = "Your City"
service_state_or_province = ""
service_post_code = "12345"
service_country = "Germany"
service_email = "mail@example.com"
service_fees = "none"
service_access_constraints = "This service is intended for research purposes only."

# EPSG Code Definitions for Matplotlib basemap               ###

# In this section you can define how EPSG codes are interpreted in
# terms of Matplotlib basemap parameters. If you require a new EPSG
# code, define it here.

# Table to translate EPSG codes to Matplotlib basemap projection parameters.
# Extend this table to add further EPSG codes.
# Also see: http://external.opengeospatial.org/twiki_public/bin/view/
#                  MetOceanDWG/MetCoordinateReferenceSystemDefinition
epsg_to_mpl_basemap_table = {
    # EPSG:4326, the standard cylindrical lat/lon projection.
    4326: {"projection": "cyl"},

    # Non-standard EPSG codes, specifically defined for MSS pruposes.
    # EPSG:777llLLL, north polar stereographic projections with lat_0=ll and
    # lon_0=LLL.
    77790000: {"projection": "stere", "lat_0": 90., "lon_0": 0.},
    77790010: {"projection": "stere", "lat_0": 90., "lon_0": 10.},
    77790015: {"projection": "stere", "lat_0": 90., "lon_0": 15.},
    77790340: {"projection": "stere", "lat_0": 90., "lon_0": -20.},
    77790105: {"projection": "stere", "lat_0": 90., "lon_0": -105.},

    77890000: {"projection": "spstere", "lat_0": 90., "lon_0": 0.},
    77890010: {"projection": "spstere", "lat_0": 90., "lon_0": 10.},
    77890015: {"projection": "spstere", "lat_0": 90., "lon_0": 15.},
    77890340: {"projection": "spstere", "lat_0": 90., "lon_0": -20.},
    77890105: {"projection": "spstere", "lat_0": 90., "lon_0": -105.}

    # Feel free to add other projections, e.g. a south polar projection
    # EPSG:778llLLL.

You have to adopt this file to your data.

Standalone server setup

For the standalone server mswms you need the path of your mss_wms_settings.py and other configuration files added to the PYTHONPATH. E.g.:

export PYTHONPATH=/home/mss/INSTANCE/config

For testing your server you can use the Simulated Data and its configuration

meteorological data

Data for the MSS server shall be provided in CF-compliant NetCDF format. Several specific data access methods are provided for ECMWF, Meteoc, and several other formats.

The prefered method “DefaultDataAccess” shall supplant most of these, but requires the data to be organised in the fashion described in the following (the others pose mostly the same requirements).

All data files belonging to one “set” shall have a common string in its name that can be used to uniquely identify all files of this set. Each set must share the same time, longitude, and latitude grid. Each set must use the same elevation layers for each type of vertical axis. Different data sets may be used to offer different geographical regions or results of different simulation models.

Each file of a set must contain only one or no vertical axis. If the data is required to be given on multiple vertical axis (such as providing data for horizontal plots on both pressure and theta levels), one (or more separate) file for each vertical axis type must be provided. All files for one axis type shall provide the same levels. If no vertical axis can be identified, it is assumed that the file contains 3-D data (time, lat, lon) such as, e.g., surface pressure or tropopause altitude.

The vertical coordinate variable is identified by the standard_name being one of the following names:

  • atmosphere_hybrid_sigma_pressure_coordinate - “ml”
  • atmosphere_pressure_coordinate - “pl”
  • atmosphere_ertel_potential_vorticity_coordinate - “pv”
  • atmosphere_altitude_coordinate - “al”
  • atmosphere_potential_temperature_coordinate - “tl”

The two-letter abbreviation is used for brief identification in the plotting routines in addition to the standard_name of the variable to uniquely identify which data shall be used. The data shall be organized with the dimensions in the order of “time”, “vertical coordinate”, “latitudes”, and “longitudes” (This is important to reduce disk access when generating the plots). Data variables are identified by their standard_name, which is expected to be CF compliant. Data variables should contain a “units” attribute that may be used by the plotting routines for checking and/or conversion. Please bear in mind that the vertical axis of all vertical sections is pressure in ‘Pa’.

It is assumed that forecast data is given from one initialisation time onward for several time steps into the future. For each file, the init time is determined by the units attribute of the “time” variable. The time variable is identified by its standard_name being “time”. The date given after “since” is interpreted as the init time such that the numerical value of “0” were the init time (which need not be present in the file). For example, if the units field of “time” contains “hours since 2012-10-17T12:00:00.000Z”, 2012-10-17T12Z would be the init time. Data for different time steps may be contained in one file or split over several ones.

An exemplary header for a file containing ozone on a vertical pressure coordinate and a 3-D tropopause would look as follows:

netcdf example_ASIA {
        press = 13 ;
        lat = 51 ;
        lon = 141 ;
        time = 12 ;
        float press(press) ;
                press:units = "Pa" ;
                press:positive = "down" ;
                press:standard_name = "atmosphere_pressure_coordinate" ;
        float lat(lat) ;
                lat:units = "degrees_north" ;
                lat:standard_name = "latitude" ;
        float lon(lon) ;
                lon:units = "degrees_east" ;
                lon:standard_name = "longitude" ;
        float time(time) ;
                time:units = "hours since 2012-10-17T12:00:00Z" ;
                time:standard_name = "time" ;
        float O3(time, press, lat, lon) ;
                O3:units = "mol/mol" ;
                O3:standard_name = "mole_fraction_of_ozone_in_air" ;
        float tropopause(time, lat, lon) ;
                tropopause:units = "Pa" ;
                tropopause:standard_name = "tropopause_air_pressure" ;

Apache server setup

Install mod_wsgi

On some distributions an old mod_wsgi is shipped and have to become replaced by a version compatible to the conda environment.

At current state we have to use pip to install mod_wsgi into the INSTANCE environment:

# Instal `mod_wsgi`
$ pip install mod_wsgi

# Find the full path to installed `mod_wsgi`
$ which mod_wsgi-express

# Install and register the `mod_wsgi` module with Apache
$ sudo /full/path/to/installed/mod_wsgi-express install-module

Setup a /etc/apache2/mods-available/wsgi_express.conf:

WSGIPythonHome "/home/mss-demo/miniconda3/envs/demo/"

Setup a /etc/apache2/mods-available/wsgi_express.load:

LoadModule wsgi_module "/usr/lib/apache2/modules/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so"

Enable the new module by a2enmod and reload the apache2 server

One Instance

Our examples are based on the following directories located in the home directory of the mss user. INSTANCE is a placeholder for your service name:

|   ├── config
│   |   └── mss_wms_settings.py
|   |   └── mss_wms_auth.py
|   ├── log
│   |   └── mss_error.log
|   └── wsgi
|       ├── auth.wsgi
|       └── wms.wsgi
├── miniconda3
│   ├── bin
│   ├── conda-bld
│   ├── conda-meta
│   ├── envs
|   |   └── instance
│   ├── etc
│   ├── include
│   ├── lib
│   ├── LICENSE.txt
│   ├── pkgs
│   ├── share
│   ├── ssl
│   └── var

Configuration of apache mod_wsgi.conf

One posibility to setup the PYTHONPATH environment variable is by adding it to your mod_wsgi.conf. Alternativly you could add it also to wms.wsgi.

WSGIPythonPath /home/mss/INSTANCE/config:/home/mss/miniconda3/envs/instance/lib/python3.X/site-packages

By this setting you override the PYTHONPATH environment variable. So you have also to add the site-packes directory of your miniconda or anaconda installation besides the config file path.

If your server hosts different instances by different users you want to setup this path in mss_wms_setting.py.

Configuration of wsgi for wms

You can setup a vhost for this service.


import os
import sys

sys.path.insert(0, '/home/mss/INSTANCE/config')
sys.path.insert(0, '/home/mss/miniconda3/envs/instance/lib/python3.7/site-packages/mslib')
sys.path.insert(0, '/home/mss/INSTANCE/wsgi')

if os.getenv("PROJ_LIB") is None or os.getenv("PROJ_LIB") == "PROJ_LIB":
    os.environ["PROJ_LIB"] = '/home/mss/miniconda3/envs/instance/share/proj'

sys.stdout = sys.stderr
import logging

from mslib.mswms.wms import app as application

Configuration of wsgi auth

As long as you have only one instance of the server running you can use this method to restrict access.

To restrict access to your data use this script.


import sys

import mss_wms_auth
import hashlib

def check_password(environ, username, password):
    for u, p in mss_wms_auth.allowed_users:
        if (u == username) and (p == hashlib.md5(password).hexdigest()):
           return True
    return False

This needs also a configuration /home/mss/INSTANCE/config/mss_wms_auth.py script.

# -*- coding: utf-8 -*-


    Configuration module for authentication to the MSS server.

    This file is part of mss.

    :copyright: 2008-2014 Deutsches Zentrum fuer Luft- und Raumfahrt e.V.
    :copyright: 2011-2014 Marc Rautenhaus
    :copyright: Copyright 2016-2017 by the mss team, see AUTHORS.
    :license: APACHE-2.0, see LICENSE for details.

    Licensed 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


    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.

# HTTP Authentication                               ###
# Use the following code to create a new md5 digest of a password (e.g. in
# ipython):
#     import hashlib; hashlib.md5("my_new_password").hexdigest()
allowed_users = [("mswms", "add_md5_digest_of_PASSWORD_here"),
                 ("add_new_user_here", "add_md5_digest_of_PASSWORD_here")]

At the moment you have many different instances with different users or different versions of mss you have to use basic auth of your webserver configuration.

Configuration of your site as vhost

You have to setup a webserver server site configuration file


<VirtualHost *:80>
    ServerName mss.yourserver.de
    #ServerAlias localwms
    LogLevel debug
    ServerAdmin webmaster@yourserver.de
    ErrorLog /home/mss/log/mss_error.log
    TransferLog /var/log/apache2/mss_access.log
    CustomLog /var/log/apache2/mss_custom.log combined

    #all wsgi scripts are in /home/mss/wsgi
    <Directory /home/mss/INSTANCE/wsgi>
        AuthType Basic
        AuthName "mss"
        AuthBasicProvider wsgi
        WSGIAuthUserScript /home/mss/INSTANCE/wsgi/auth.wsgi
        AuthDigestDomain / http://mss.yourserver.de/
        Require valid-user

    #alternative without authentication
    #<Directory /home/mss/INSTANCE/wsgi>
    #    Require all granted

    WSGIScriptAlias / /home/mss/INSTANCE/wsgi/wms.wsgi
    WSGIDaemonProcess MSS python-home=/home/mss/miniconda3/env/instance/bin user=mss group=mss home=/home/mss/INSTANCE/config processes=2 threads=1 deadlock-timeout=25 display-name=MSS
    # WSGI Options
    # python-home: path where your environment python bin is located
    # home: where your config scripts are located
    # Please see the documentation here : http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives
    WSGIProcessGroup MSS

    # Python Simplified GIL State API ( ISSUE ) 
    # http://code.google.com/p/modwsgi/wiki/ApplicationIssues
    WSGIApplicationGroup %{GLOBAL} 

    DocumentRoot /home/mss/htdocs

    # don't loose time with IP address lookups
    HostnameLookups Off

    # needed for named virtual hosts
    UseCanonicalName Off

    # configures the footer on server-generated documents
    ServerSignature On


Enable it with a2ensite mss.yourserver.de.conf

Many Instances

If you want to setup many instances we suggest to use a similiar proxy based configuration

<VirtualHost *:80>

ProxyRequests Off
ProxyPreserveHost On
RewriteEngine On
RequestHeader add X-SSL off
RewriteRule ^/demo/(.*)$1 [P,L]

ServerName proxy_demo.yourserver.de
DocumentRoot /var/www/html
ProxyPreserveHost On
ProxyPass /demo


and if you need authentication then use a Location based AuthType Basic


    #<Directory /home/mss/DEMO/wsgi>
    #    Require all granted

     <Location /demo>
        AuthType Basic
        AuthName "mss"
        AuthDigestDomain /
        AuthUserFile /home/mss/DEMO/config/apache_users 
        <Limit GET>
            Require valid-user
    # You can define different hostname for the WMS VirtualHost here
    # For example, you can add localwms to your /etc/hosts file along with localhost
    # and use the 'localwms' name like in the template here.
    ServerName proxy_demo.yourserver.de
    RemoteIPHeader X-Forwarded-For
    LogLevel error
    ServerAdmin admin@email
    ErrorLog /home/mss/DEMO/log/mss_error.log
    TransferLog /var/log/apache2/mss_access.log
    CustomLog /var/log/apache2/mss_custom.log combined

    # WSGI Options
    # home: Initial working directory of the script, make sure you change it.
    # python-path : Directories to search for Modules, make sure you change it as well.
    # Please see the documentation here : http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives 
    WSGIScriptAlias /demo /home/mss/DEMO/wsgi/wms.wsgi

    WSGIDaemonProcess MSSDEMO python-home="/home/mss/miniconda3/envs/demo" python-path="/home/mss/miniconda3/envs/demo/lib/python3.7/site-packages/" home="/home/mss/DEMO/config"  user=mss group=mss processes=2 threads=1 deadlock-timeout=25 display-name=MSSDEMO

    WSGIProcessGroup MSSDEMO

    # Python Simplified GIL State API ( ISSUE ) 
    # http://code.google.com/p/modwsgi/wiki/ApplicationIssues
    WSGIApplicationGroup %{GLOBAL} 

    # don't loose time with IP address lookups
    HostnameLookups Off

    # needed for named virtual hosts
    UseCanonicalName Off

    # configures the footer on server-generated documents
    ServerSignature On


For further informations on apache2 server setup read https://httpd.apache.org/docs/2.4/howto/