Website

Our website module provides everything you need, to manage, deploy and run your website. It is type and environment based which means you have to select a particular type (e.g. typo3cms) and environment (e.g. PROD). According those settings, our automation will setup the server/vhost as required.

Add website

Add a website with a configuration like this:

website::sites:
  "username":
    "server_name": "example.net www.example.net"
    "env":         "PROD"
    "type":        "php"
  • username: Is used as system user name (SSH Login, CGI User) and database name, if a database exist
  • 2 - 16 lowercase letters only (as this name is used in several places, we have to limit its value to the least common denominator)
  • server_name: add host names which this vhost will listen on. You have to define all names explicit, also with and/or without www.
  • env: One of DEV, STAGE or PROD (see Environments)
  • type: software type of this particular website (see Types)

By adding a website, the following parts are created on the server:

  • system user
  • system group
  • home directory (/home/username/)
  • directory for temporary files (/home/username/tmp/)
  • directory for log files (/home/username/log/)
  • directory for additional configuration files (/home/username/cnf/)
  • directory for backups (used for database dumps, /home/username/backup/)
  • environment variables for bash and zsh (~/.profile and ~/.zprofile)
  • SSH authorised keys
  • webserver vhost configuration (for custom configurations, see Custom configuration)

Types

You have to define one of the following types for each website.

Note

If you need a type not mentioned here yet, do not hesitate to contact us

typo3cmsv7

  • nginx with WAF, core rule set and TYPO3 7.x compatible white/blacklists
  • PHP 7.2
  • MariaDB 10.x with database, user, and grants
  • latest TYPO3 CMS 7 LTS cloned into /opt/typo3/TYPO3_7/
  • Default webroot is ~/web
  • PHP-Settings adjusted to 7.x-requirements
  • TYPO3 ApplicationContext can be set by setting applicationContext in custom JSON
  • TYPO3 Scheduler executed every 5 minutes

typo3cmsv8

  • nginx with WAF, core rule set and TYPO3 8.x compatible white/blacklists
  • PHP 7.2
  • MariaDB 10.x with database, user, and grants
  • latest TYPO3 CMS 8 LTS cloned into /opt/typo3/TYPO3_8/
  • Default webroot is ~/web
  • PHP-Settings adjusted to 8.x-requirements
  • TYPO3 ApplicationContext can be set by setting applicationContext in custom JSON
  • TYPO3 Scheduler executed every 5 minutes

typo3cmsv9

  • nginx with WAF, core rule set and TYPO3 9.x compatible white/blacklists
  • PHP 7.2
  • MariaDB 10.x with database, user, and grants
  • latest TYPO3 CMS 9 LTS cloned into /opt/typo3/TYPO3_9/
  • Default webroot is ~/web
  • PHP-Settings adjusted to 9.x-requirements
  • TYPO3 ApplicationContext can be set by setting applicationContext in custom JSON
  • TYPO3 Scheduler executed every 5 minutes

neos

  • nginx 1.6 with naxsi WAF, core rule set and TYPO3 Neos white/blacklists
  • PHP-FPM 5.6
  • MariaDB 10.x with database, user, and grants
website::sites:
  "neosexample":
    "password":    "Efo9ohh4EiN3Iifeing7eijeeP4iesae"
    "server_name": "neos.example.net www.neos.example.net"
    "env":         "PROD"
    "type":        "neos"

magento2

The magento2 type is not yet ready on version 6. Please use a server of version 5 to host Magento 2 sites by now.

wordpress

  • nginx 1.6 with naxsi WAF, core rule set and WordPress white/blacklists
  • PHP-FPM 5.6
  • MariaDB 10.x with database, user, and grants
  • WP-CLI
website::sites:
  "wordpressexample":
    "server_name": "wordpress.example.net"
    "env":         "PROD"
    "type":        "wordpress"
    "password":    "Aiw7vaakos04h7e"

php72

  • nginx 1.6 with naxsi WAF and core rule set
  • PHP-FPM 5.6
  • MariaDB 10.x with database, user, and grants (use “dbtype”: “mysql”, otherwise without database)
website::sites:
  "phpexamplenet":
    "server_name": "php.example.net"
    "env":         "PROD"
    "type":        "php"
# uncomment the following lines if you need an automatically created database
#    "dbtype":      "mysql"
#    "password":    "Aiw7vaakos04h7e"

html

  • nginx 1.6 with naxsi and core rule set
  • for static content only (this documentation is served trough the html type)
website::sites:
  "htmlexamplenet":
    "server_name": "html.example.net"
    "env":         "PROD"
    "type":        "html"

uwsgi

  • nginx 1.6 with naxsi WAF and core rule set

  • uwsgi Daemon (Symlink your appropriate wsgi configuration to ~/wsgi.py)

  • Python virtualenv venv-<sitename> configured within uwsgi and the user login shell

  • there is no database added by default, choose one of

  • PostgreSQL 9.4 with database, user, and grants ("dbtype": "postgresql")

  • MariaDB 10.x with database, user, and grants ("dbtype": "mysql")

  • all requests are redirected to the uwsgi daemon by default. To serve static files, add appropriate locations to the Custom configuration like this:

    location /static/
    {
    root /home/user/application/;
    }
    
website::sites:
  "uwsgiexample":
    "server_name": "uwsgi.example.net"
    "env":         "PROD"
    "type":        "uwsgi"
    "dbtype":      "postgresql"
    "password":    "ohQueeghoh0bath"

Hint: to control the uwsgi daemon, use the uwsgi-reload and uwsgi-restart shortcuts

Redirect

  • redirects everything to a custom target
  • by default, we send a 307 HTTP code. To use your own code, add the target_code parameter to the websites custom JSON:
{
  "target_code": "301"
}

Hint

you can use any nginx variable as target (for example $scheme://www.example.com$request_uri), see the nginx Documentation for available variables

Proxy

  • nginx vhost configured as reverse proxy
"proxyexample":
 "server_name": "example.net"
 "env":         "PROD"
 "type":        "proxy"
 "proxy_pass":  "http://127.0.0.1:8080"

Hint

to use advanced features or multiple backends, create your own upstream configuration in /etc/nginx/custom/http.conf and point proxy_pass to it

nodejs

  • nodejs daemon, controlled by monit

  • select custom node version trough nvm, by default, the latest node lts version is installed

  • symlink your app.js to ~/app.js or overwrite path or other daemon options in OPTIONS at ~/cnf/nodejs-daemon:

    OPTIONS="/home/nodejs/application/app.js --prod"
    
  • nodejs has to listen on the ~/cnf/nodejs.sock socket, permission 660

  • there is no database added by default, choose one of
    • PostgreSQL with database, user, and grants ("dbtype": "postgresql")
    • MariaDB with database, user, and grants ("dbtype": "mysql")
  • all requests are redirected to the nodejs daemon by default. To serve static files, add appropriate locations to the Custom configuration like this:

    location /static/ {
      root /home/user/application/;
      include /etc/nginx/custom/security.conf;
    }
    

Hint

to control the nodejs daemon, use the nodejs-restart shortcut

ruby

  • nginx 1.6 with naxsi WAF and core rule set

  • Python virtualenv venv-<sitename> configured within the user login shell

  • ruby rbenv configured within foreman and the user login shell

  • foreman daemon, controlled by monit

  • symlink your Procfile to ~/ or overwrite path or other daemon options in OPTIONS at ~/cnf/ruby-daemon:

    OPTIONS="start web -f project/Procfile"
    
  • ruby has to listen on the ~/cnf/ruby.sock socket, permission 660

  • there is no database added by default, choose one of
    • PostgreSQL 9.4 with database, user, and grants ("dbtype": "postgresql")
    • MariaDB 10.x with database, user, and grants ("dbtype": "mysql")
  • all requests are redirected to the ruby daemon by default. To serve static files, add appropriate locations to the Custom configuration like this:

    location /static/
    {
        root /home/user/application/;
    }
    
website::sites:
  "ruby":
    "server_name": "ruby.example.net"
    "env":         "PROD"
    "type":        "ruby"
    "dbtype":      "mysql"
    "password":    "ohQueeghoh0bath"

Hint

to control the nodejs daemon, use the ruby-start / ruby-stop / ruby-restart shortcuts

Environments

You have to select one of those environments for each website:

PROD

  • for live sites
  • no access protection
  • phpinfo disabled (visible database credentials from environment variables)
  • E-Mails get sent to their designated recipient (PHP mail() only, see E-Mail Handling for details)

STAGE

  • for stage / preview / testing access
  • password protected (User “preview”, password from “preview_htpasswd” option)
  • phpinfo enabled
  • E-Mails get saved as file into the ~/tmp/ directory (PHP mail() only, E-Mail Handling for details)

DEV

  • for development
  • password protected (User “preview”, password from “preview_htpasswd” option)
  • phpinfo enabled
  • Xdebug enabled, see PHP debugging for details)
  • E-Mails get saved as file into the ~/tmp/ directory (PHP mail() only, E-Mail Handling for details)

User Handling

The preview user gets applied to all non PROD environments and is intended for your own use, but also to allow access to other parties like your customer. Use the “preview_htpasswd” option to set a particular password to the preview user. You have to use a htpasswd encrypted value which you can generate like this on your local workstation:

htpasswd -n preview

Configuration example:

"devexamplenet":
    "type":             "typo3cms"
    "env":              "DEV"
    "server_name":      "dev.example.net www.dev.example.net"
    "password":         "1234"
    "preview_htpasswd": "$apr1$RSDdas2323$23case23DCDMY.0xgTr/"

Furthermore, you can add additional users trough the “website::users” configuration like this:

website::users:
  "alice":
    "preview": "$apr1$RXDs3l18$w0VJrVN5uoU6DMY.0xgTr/"
  "bob":
    "preview": "$apr1$RSDdas2323$23case23DCDMY.0xgTr/"

You can add such uers for yourself and your co-workers. If you work on multiple websites, you do not have to look up the corresponding password all the time but just use the global one.

To rename the default “preview” username, use the preview_username parameter on a website:

"devexamplenet":
    "type":             "typo3cms"
    "env":              "DEV"
    "server_name":      "dev.example.net www.dev.example.net"
    "password":         "1234"
    "preview_username": "showme"
    "preview_htpasswd": "$apr1$RSDdas2323$23case23DCDMY.0xgTr/"

Furthermore, its possible to set the preview username globally through website::preview_username.

Note

Please keep in mind that this password gets often transfered over unencrypted connections. As always, we recommend to use a particular password for only this purpose

Disable exeptions

Never show detailed application based exeptions on PROD, to avoid information leakage. Disable the output directly in your application. For example in TYPO3:

$TYPO3_CONF_VARS['SYS']['displayErrors'] = '0';

Environment Variables

For each website, the following environment variables are created by default, and are available within the shell and also the webserver.

  • SITE_ENV (DEV, STAGE or PROD)
  • DB_HOST (Database hostname, only if there is a database)
  • DB_NAME (Database name, only if there is a database)
  • DB_USERNAME (Database username, only if there is a database)
  • DB_PASSWORD (Database password, only if there is a database)

Hint

to use the .profile environmet within a cronjob, prepend the following code to your command: /bin/bash -c 'source $HOME/.profile; ~/original/command'

Example usage within PHP

As soon there is a database installed, the following variables are added to the environment and can be used from within your application. TYPO3 Example:

$typo_db_username = $_SERVER['DB_USERNAME'];
$typo_db_password = $_SERVER['DB_PASSWORD'];
$typo_db_host     = $_SERVER['DB_HOST'];
$typo_db          = $_SERVER['DB_NAME'];

Additionaly, you can use the “SITE_ENV” variable to set parameters according the current environment:

switch ($_SERVER['SITE_ENV']) {
    case 'DEV':
        $recipient = 'dev@example.net';
        break;
    case 'STAGE':
        $recipient = 'dev@example.net';
        break;
    case 'PROD':
        $recipient = 'customer@example.com';
        break;
}

If you configure your application like this, you can copy all data between different servers or vhosts (DEV/STAGE/PROD) and all settings are applied as desired.

Example usage within typoscript

[globalString = _SERVER|SITE_ENV = DEV]
    # doSometing
[global]

TLS Certificates

By adding a TLS certificate to your website, the following configurations/features are applied to the vhost:

  • SPDY 3.1
  • TLS 1.0, 1.1, 1.2
  • SNI
  • HSTS
  • daily Expiration Date Check
  • daily Qualys SSL Labs API Check
  • global HTTP to HTTPS redirect

Automated Certificate Management Environment (ACME/Let’s Encrypt)

We support ACME certificates by Let’s Encrypt. To enable this, set ssl_acme to true. You can select a specific ACME provider by ssl_acme_provider, however by now only letsencrypt is available and already set as default, so you can omit this usually.

Warning

Let’s Encrypt will try to reach your server by HTTP. Make sure that all hosts added to server_name end up on your server already, otherwise validation will fail

Debug validation problems

In order to debug validation issues, we introduced the letsencrypt-renew shortcut which will trigger a run of our Let’s Encrypt client, and let you see all debug output to identifiy possible problems.

Renewal

Certificates from Let’s Encrypt will be valid for 90 days. They are renewed automatically as soon as they expire in under 30 days. You can follow these checks and renewals by grep for letsencrypt in /var/log/syslog.

Furthermore, we check all certificates from our monitoring and will contact you if there are certificates expiring in less than 21 days.

Configuration example

"devexamplenet":
  "type":              "html"
  "env":               "PROD"
  "ssl_acme":          "true"
  "ssl_acme_provider": "letsencrypt" # not required, as letsencrypt is already the default
  "server_name":       "dev.example.net www.dev.example.net" # make sure that all this hosts
                                                             # point to this server already

Order certificate

Requirements

To validate domain ownership, our certificate issuer will send a E-Mail to one of the following addresses:

Create certificate and key

$ openssl req -newkey rsa:4096 -x509 -nodes -days 3650 -out www.example.net.crt -keyout www.example.net.key
Country Name (2 letter code) [AU]:CH
State or Province Name (full name) [Some-State]:Luzern
Locality Name (eg, city) []:Luzern
Organization Name (eg, company) [Internet Widgits Pty Ltd]:example Ltd
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:www.example.net
Email Address []:webmaster@example.net

Extract certificate signing request

openssl x509 -x509toreq -signkey www.example.net.key -in www.example.net.crt

Submit this CSR to us for further processing, or order certificate by yourself from the issuer of your choice.

Configure website

  • ssl_key: generated private key
  • ssl_cert: signed certificate, including appropriate intermediate certificates

Note: make sure to use the correct line indenting

website::sites:
  "magentoexample":
    "server_name": "magento.example.net"
    "env":         "PROD"
    "type":        "magento"
    "password":    "Aiw7vaakos04h7e"
    "ssl_key": |
     -----BEGIN PRIVATE KEY-----
     MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRHc47/0zg+cAg
     MkHKY1U7TOFChPawiNmU94MYjOTzK/Lc4C2op1sDCAP4Ow+qx7BK8NLJkHUPyOXU
     zjTTTUN/dGoElGz4gFaCCkMhk8hRZEs8jTwAm8tq4ruUVk9DIgQ9K/potm5kzT/T
     KyW85hETMLi+tRw9Kbn/j4QljWmqcd4mPWyaMT1o4lDTszq7NCHGch+dxa4FJYib
     z05C6+BVpw9w+BWFERlbgG5hvMMXtxexlju24e2fJV/TPCVbgDk/ecFDhupRMdj9
     ZKrlPcUZNReWxgX+ZGT8YfWI2tYfW9+H6iVvcwV2gftiDp4+N4r4Ae52cMFxcKBR
     5fn4i8hbAgMBAAECggEAYv66MBr3GRYChvtju9z0b2NAzE3HvuC6KFRYAlpI1Hl8
     umWCF/JKGpBD2NKU4yMvaPrCvtsdH8DaVLjdtx4/kunYepyNTcLrsRoMl6uvTCCv
     oVW3Dw6x6MK3TEzjrwM+gHr+S235qsyjp2MotVkwwiXxf46bdLT5MWuOgnyEhkQQ
     cmv6qDmjgDP5vH5r4riAlPKMq+jGtLc/2QWs22UxQS0/a7n0pks472AonLI8r1M8
     sYcCAC6uEvxRZdVcJOlRK78dPI3NLxFhSbvv/GcVOypyhvQ3uVYV71xA/AgcpBLd
     Fc2FULRCCU/UEjmo62uYNkG09lCchBwK8BLYYWrCoQKBgQDqL5Eo9oLMTuzysu8I
     vemXODOTfxQb1OTH1dyA4kSAtmNF2IO5rNnvVsS5YlbSjZMEXRMYTSflp7L7ae2l
     XLqjhijdB6l5cdgsPyHD2phYOvJzWMuzjkCtIjm5QfdMfsUZnBSPbwaRF1zWxbVn
     mHlWi3Zcu8U65l9gsJviZelqqwKBgQDkmG4W1SEySON4i44JwzsmXQHP1d8KHES1
     hB1IETNYV2HzIAWnnX/fqPwqyahzegKTGut9U7kJ8QHsHvz56nHdiQ8zw4BZxQPw
     j4Pms1IpzpO48yf4swskqwgkk5W6wTHCD/Q48tqFtAMPwC/D88F966ipc6lyldsJ
     UXvLeeMZEQKBgGTHYZWaOAGKOYfcHufJKnwMEI350wKDJI0m6ISCWu51DtWg7lb6
     HrNTyMbqnehwSoNHNo9vrKq0914gYMeX1y3F71HnGTSNHHU2Gea57HOTsoCXBtpX
     blfTcbnavHyr1VBHDcYIBnBr+GTooj9Zq2XmEGKp35+QQh1PA1ZzevaPAoGASdop
     Lv06VVmRC9/iSqslT/aaYEATZ9vMIuyE3USZVwAcKAT/brCGoIaiuVwfLPeNH2OC
     EyJaVKjlWxiD2GXy1YSzQaD2tYneBPkIvx7N+62+sfD0x/doMTeEUPTRWd2SqsSm
     vUNQcAPBPXR0uhTlPT5GZkB0zQ03D6KgoRNG2FECgYEAgXPJjIsqhcC0PNEuRgdC
     9pZq+Prvp4TniVwQKyPniw/FjAplI4uN/+1fiYPexaLzINnXUuvOTYPABec3T2DZ
     GV0lffmdZ+CleU1Xi5DjLGn8m0Gdy6mecE2Le9/Q13o3owF9rm0Drhqqd8T6vVt3
     hiw7C+lCp2XheqP+QchwxiY=
     -----END PRIVATE KEY-----
    "ssl_cert": |
     -----BEGIN CERTIFICATE-----
     MIIEATCCAumgAwIBAgIJAMdVCMOVZl30MA0GCSqGSIb3DQEBCwUAMIGWMQswCQYD
     VQQGEwJDSDEPMA0GA1UECAwGWnVyaWNoMQ8wDQYDVQQHDAZadXJpY2gxIzAhBgNV
     BAoMGnNub3dmbGFrZSBwcm9kdWN0aW9ucyBHbWJIMRowGAYDVQQDDBF0eXBvMy5l
     eGFtcGxlLm5ldDEkMCIGCSqGSIb3DQEJARYVd2VibWFzdGVyQGV4YW1wbGUubmV0
     MB4XDTE1MDIxMjEyMDU1MloXDTI1MDIwOTEyMDU1MlowgZYxCzAJBgNVBAYTAkNI
     MQ8wDQYDVQQIDAZadXJpY2gxDzANBgNVBAcMBlp1cmljaDEjMCEGA1UECgwac25v
     d2ZsYWtlIHByb2R1Y3Rpb25zIEdtYkgxGjAYBgNVBAMMEXR5cG8zLmV4YW1wbGUu
     bmV0MSQwIgYJKoZIhvcNAQkBFhV3ZWJtYXN0ZXJAZXhhbXBsZS5uZXQwggEiMA0G
     CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRHc47/0zg+cAgMkHKY1U7TOFChPaw
     iNmU94MYjOTzK/Lc4C2op1sDCAP4Ow+qx7BK8NLJkHUPyOXUzjTTTUN/dGoElGz4
     gFaCCkMhk8hRZEs8jTwAm8tq4ruUVk9DIgQ9K/potm5kzT/TKyW85hETMLi+tRw9
     Kbn/j4QljWmqcd4mPWyaMT1o4lDTszq7NCHGch+dxa4FJYibz05C6+BVpw9w+BWF
     ERlbgG5hvMMXtxexlju24e2fJV/TPCVbgDk/ecFDhupRMdj9ZKrlPcUZNReWxgX+
     ZGT8YfWI2tYfW9+H6iVvcwV2gftiDp4+N4r4Ae52cMFxcKBR5fn4i8hbAgMBAAGj
     UDBOMB0GA1UdDgQWBBSSJyPyLa8CNKMDR3BAOcuuzzEqlTAfBgNVHSMEGDAWgBSS
     JyPyLa8CNKMDR3BAOcuuzzEqlTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
     A4IBAQAMKv2Kdw2LkskJm/GAkEsavoYf6qAPruwcsp8cx+7doXOpptZ/w+m8NK8i
     6ffi65wQ4TGlFxEvXM1Ts4S1xF/+6JVnnp8RVGvfgDL7xi6juMqbtM5yBxjHKO6W
     AuxOmwPcd6cO5qL+MCSgIe13bn/V4bw/JLv9LONuwXHJuv0FEoazbSyB6uTwYf2D
     pWHEkXvkz5A1hqu3y2jFq2cQffoO31GGx29U3uSl+Esp5bL/J0bQd3TUbwvu6FY1
     NgUR7Mx1t/4/uk9FRl87d2rRslc5VyvD5v7MFE6jYJap74j5BrrfUUTNbzVXdPCS
     v8jOaIjDp5AMoZxbPMlv/5Tk85uF

Warning: Make sure the first server_name used is valid within your certificate as we redirect all HTTP requests within this vHost to https://first-in-server_name

Multi domain certificates

As all HTTP requests within a given vHost are redirected to HTTPS using the first name in server_name, you have to add manual redirects (Redirect type) for additional domains in a multi domain certificate. Make sure those redirects are evaluated before the default redirect vHost, for example by adding a aaa prefix to their name.

website::sites:
  # without this redirect, the default HTTP>HTTPS redirect mechanism
  # will use the first server_name (www.example.com) instead of www.example.net
  "aaanetredirect":
    "server_name": "www.example.net example.net"
    "target": "https://$host$request_uri"
    "env": "PROD"
    "type": "redirect"
  "examplecom":
    "server_name": "www.example.com example.com www.example.net example.net"
    "env": "PROD"
    "ssl_key": "multi domain certificate for www.example.com and www.example.net here"

HTTP redirect

By default, all HTTP requests within a given vHost are redirected to HTTPS keeping the hostname supplied by the client. If you want to change this behaviour somehow, for example by always redirect to the first hostname of the vhost, you can set http_redirect_dest to another value like https://$server_name$request_uri.

Furthermore, its possible to set the redirect destination globally through website::http_redirect_dest which will be used on all HTTP redirects without a explicitly set http_redirect_dest.

~/cnf/nginx-redirect.conf

Included within the server block of each HTTP to HTTPS redirect. You can use this file to configure specific redirect rules and settings.

Cipher Suite

You can configure a desired cipher suite configuration trough website::ssl_ciphers:

website::ssl_ciphers: "desired-cipher-suites"

Warning

We configure and update this value with sane defaults. Overwrite only when really required, and if you are aware of the consequences.

Diffie-Hellman parameters

Diffie-Hellman parameters are used for perfect forward secrecy. We supply default Diffie-Hellman parameters and update them on a regular schedule. If you want to use your own Diffie-Hellman parameters, you can generate them:

openssl dhparam -out /tmp/dhparam.pem 4096

and configure them trough website::ssl_dhparam:

website::ssl_dhparam: |
  -----BEGIN DH PARAMETERS-----
  MIICCAKCAgEAoOePp+Uv2M34IA+basW9CBHp/jsZihB3FI8KVRLVFJPIUJ9Llm8F
  ...
  -----END DH PARAMETERS-----

HSTS Header

By default, we add a HTTP Strict Transport Security (HSTS) header to each TLS enabled website:

Strict-Transport-Security max-age=63072000;

Use the header_hsts parameter to override the default HSTS header:

header_hsts: "max-age=3600; includeSubDomains; preload"

Hint

See the OWASP HTTP Strict Transport Security Cheat Sheet for details

Test

We recommend the following online services for testing:

Web Application Firewall

We use Naxsi as additional protection against application level attacks such as cross site-scripting or SQL injections. We also block common vulnerabilities and zero day attacks, see our status site for updates.

Warning: this is just a additional security measure. Regardless its existence, remember to keep your application, extensions and libraries secure and up to date

Identify blocks

If a request is blocked, the server will issue a “403 forbidden” error. There are detailed informations available in the error log file:

2015/02/17 14:03:04 [error] 15296#0: *1855 NAXSI_FMT: ip=192.168.0.22&server=www.example.net&uri=/admin/&learning=0&vers=0.53-1&total_processed=1&total_blocked=1&block=1&cscore0=$XSS&score0=8&zone0=BODY|NAME&id0=1310&var_name0=login[username]&zone1=BODY|NAME&id1=1311&var_name1=login[username], client: 192.168.0.22, server: www.example.net, request: "POST /admin/ HTTP/1.1", host: "www.example.net", referrer: "http://www.example.net/admin/"

To learn more about the log syntax, vist the Naxsi documentation.

Extensive logging

If you want to debug the WAF block (often used with internal rules), you can increase the nginx error log level to “debug”.

See Nginx documentation error log for more information.

Manage false positives

If you are certain, that your request to the application is valid (and well coded), you can whitelist the affected rule(s) within the ~/cnf/nginx_waf.conf File:

BasicRule wl:1310,1311 "mz:$ARGS_VAR:tx_sfpevents_sfpevents[event]|NAME";
BasicRule wl:1310,1311 "mz:$ARGS_VAR:tx_sfpevents_sfpevents[controller]|NAME";

See the Naxsi documentation for details.

Hint

to apply the changes reload the nginx configuration with the nginx-reload shortcut

Hint

we strongly recommend to add the ~/cnf/ directory to the source code management of your choice

Autocreate rules

With nx_util, you can parse & analyze naxsi log files. It will propose appropriate whitelist rules tailored to your application

Warning: Use on DEV/STAGE Environment only. Otherwise you will end up whitelisting actual attacks.

/usr/local/bin/nx_util.py -lo error.log

Deleting old database :naxsi_sig
List of imported files :['error.log']
Importing file error.log
        Successful events :6
        Filtered out events :0
        Non-naxsi lines :0
        Malformed/incomplete lines 5
End of db commit...
Count (lines) success:6
########### Optimized Rules Suggestion ##################
# total_count:2 (33.33%), peer_count:1 (100.0%) | ], possible js
BasicRule wl:1311 "mz:$URL:/events/event/|$ARGS_VAR:tx_sfpevents_sfpevents[controller]|NAME";
# total_count:2 (33.33%), peer_count:1 (100.0%) | [, possible js
BasicRule wl:1310 "mz:$URL:/events/event/|$ARGS_VAR:tx_sfpevents_sfpevents[controller]|NAME";
# total_count:1 (16.67%), peer_count:1 (100.0%) | ], possible js
BasicRule wl:1311 "mz:$URL:/events/event/|$ARGS_VAR:tx_sfpevents_sfpevents[event]|NAME";
# total_count:1 (16.67%), peer_count:1 (100.0%) | [, possible js
BasicRule wl:1310 "mz:$URL:/events/event/|$ARGS_VAR:tx_sfpevents_sfpevents[event]|NAME";

Learning Mode

To enable the Naxsi learning mode, set the Naxsi flag in the ~/cnf/nginx.conf file:

set $naxsi_flag_learning 1;

Which means that Naxsi will not block any request, but logs the “to-be-blocked” requests in your ~log/error.log.

Warning: Use on DEV/STAGE Enviroment only. Otherwise you will end up with an unprotected installation.

Make sure, that you analyze the error.log carefully and only whitelist valid requests afterwards.

Dynamic configuration

Naxsi supports a limited set of variables, that can override or modify its behavoir. You can use them in your ~/cnf/nginx.conf file. For example, enable the learning mode for an specific ip:

if ($remote_addr = "1.2.3.4") {
 set $naxsi_flag_learning 1;
}

More on the dynamicmodifiers page.

Hint

this is a powerful feature in use with the nginx vars <http://nginx.org/en/docs/varindex.html>

Request limits

The number of connections and requests are limited to ensure that a single user (or bot) cannot overload the whole server.

Limits

  • 50 connections / address
  • 50 requests / second / address
  • 150 requests / second (burst)
  • >150 requests / second / address (access limited)

With this configuration, a particular visitor can open up to 50 concurrent connections and issue up to 50 requests / second.

If the visitor issues more than 50 request / second, those requests are delayed and other clients are served first.

If the visitor issues more than 150 request / second, those requests will not processed anymore, but answered with the 503 status code.

Adjust limits

To adjust this limits (e.g. for special applications such as API calls, etc), set a higher “load zone” in your local configuration (~/cnf/nginx.conf):

# connection limits (e.g. 75 connections)
limit_conn addr 75;

# limit requests / second: (small, medium, large)
limit_req zone=medium burst=500;
limit_req zone=large burst=1500;

Hint

to apply the changes reload the nginx configuration with the nginx-reload shortcut

Zones

  • small = 50 requests / second (burst: 150req/sec)
  • medium = 150 requests / second (burst: 500 req/sec)
  • large = 500 requests / second (burst: 1500 req/sec)

Note: the default zone is “small” and will fit most use cases

Warning

in SPDY, each concurrent request is considered a separate connection

Hint

for Details, see the Module ngx_http_limit_req_module documentation

Custom configuration

nginx

You can add specific configurations like redirects or headers within the ~/cnf/ directory.

Warning

You have to reload nginx after changes with the nginx-reload shortcut

~/cnf/nginx.conf

Included within the server block and used to configure specific redirects, enable gzip and other stuff directly in the nginx.conf.

if ($http_host = www.example.net) {
    rewrite (.*) http://www.example.com;
}

or you can password protect a subdirectory:

location ~* "^/example/" {
    auth_basic "Example name";
    auth_basic_user_file /home/user/www/example/.htpasswd;
    root /home/user/www/;
}

or add a IP protection:

allow <your-address>;
allow 2a04:503:0:102::2:4;
allow 91.199.98.23;
deny all;

Hint

Always allow access from 91.199.98.23 and 2a04:503:0:102::2:4 (monitoring)

or add custom MIME types:

include mime.types;
types {
    text/cache-manifest appcache;
}

if you like to run PHP in this subdirectory, don’t forget to add this nested in the location section from the example on top:

location ~ \.php {
    try_files /dummy/$uri @php;
}

Hint

for Details, see the Server Block Examples and Rewrite Rule documentation

~/cnf/nginx-prod.conf

Included within the server block on each website with environment set to PROD. For configuration examples, see the description of ~/cnf/nginx.conf above.

~/cnf/nginx-stage.conf

Included within the server block on each website with environment set to STAGE. For configuration examples, see the description of ~/cnf/nginx.conf above.

~/cnf/nginx-dev.conf

Included within the server block on each website with environment set to DEV. For configuration examples, see the description of ~/cnf/nginx.conf above.

~/cnf/nginx_waf.conf

Configure WAF exeptions here, see Web Application Firewall for details.

/etc/nginx/custom/http.conf

This file is directly integrated in http { }, before server { } and can only be edited with the devop user. You can use this file for settings that must be configured at nginx http context.

custom configuration include

Include your own, external configuration files within server { } or http { }.

  • server level: set nginx::global_config::server_file
  • http level: set nginx::global_config::http_file

Hint

with this setting, you can deploy own, system wide configuration files from a Git repository. See Globalrepo Service for details.

custom webroot

By default, the webroot directory location is choosen according vendor recommendations, depending on the selected type.

Some deployment workflows require other locations, which you can select through the custom_webroot parameter, relative to the home directory.

Warning

by now, the directory specified here needs to be a real directory (no symlinks allowed)

website::sites:
  "username":
    "server_name":    "example.net www.example.net"
    "env":            "PROD"
    "type":           "php"
    "custom_webroot": "deploy/current/html"

custom log format

To alter the format used for nginx access logs, for example due to privacy reasons, you can use the website::wrapper::nginx::log_format configuration.

This configuration is only available globally for all websites on a server, to change to default “combined” format to replace the actual visitors ip address with 127.0.0.1, use the following example:

website::wrapper::nginx::log_format: "127.0.0.1 - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\""

PHP

You can set custom PHP configurations trough the ~/cnf/php.ini file. See the PHP Documentation for details.

memory_limit = 1G
extension = ldap.so

Hint

list available extensions in /opt/php/php72/lib/php/extensions/no-debug-non-zts-20170718/

node

Warning

use only to enable node within another website type for actions like gulp. To run your own node based website, use the nodejs type

To execute custom node commands (for example gulp), add nvm (Node Version Manager) to any website by setting the following custom JSON:

{
  "nvm": true
}

By default, the latest node lts version will be installed, however you can also install and select any other version.

$ nvm ls-remote
$ nvm install <version>

Hint

see the nvm readme for details

security configuration

Access to certain private files and directories like .git is forbidden by including the global /etc/nginx/custom/security.conf file within the vhost configuration.

This file also contains the following security headers:

  • add_header X-Frame-Options "SAMEORIGIN" always;
  • add_header X-Content-Type-Options "nosniff" always;
  • add_header X-XSS-Protection "1; mode=block always";
  • add_header Referrer-Policy "strict-origin-when-cross-origin" always;

You can disable this include by setting security_conf to false within the custom JSON configuration. If you disable this, we recommend to copy the content into your own nginx.conf and adjust it to your own needs (you can view the content with the devop user). Please be aware of any ramifications, and do not disable this settings unless you absolutely know what you’re doing.

Warning

make sure to deny access to private files and directories manually, or include our global security locations from /etc/nginx/custom/security.conf within your own configuration.

Cronjobs

Add custom cronjobs through the crontab -e command:

SHELL=/usr/local/vzscripts/sfoutputtosyslog
PHP_INI_SCAN_DIR=:/etc/php5/cli/user/<username>/

#       +------------------------------------ minute (0 - 59)
#       |       +---------------------------- hour (0 - 23)
#       |       |       +-------------------- day of month (1 - 31)
#       |       |       |       +------------ month (1 - 12)
#       |       |       |       |       +---- day of week (0 - 6) (Sunday=0 or 7)
#       |       |       |       |       |

#       10      2       *       *       *       <command>

        5       *       *       *       *       <path-to-job>

Hint

For PHP based jobs, please set PHP_INI_SCANDIR manually to make sure that user specific settings are respected

Listen

By default, nginx will bind to the primary IP address of the eth0 interface and the 80/443 port. You can specify listen options explicitly per website, for example within setups where Varnish is used and the nginx vhost does not have to listen on external interfaces.

website::sites:
  "username":
    "env":                 "PROD"
    "type":                "php"
    "listen_ip":           "127.0.0.1"
    "listen_port":         "8080"
    "listen_options":      "option_value"
    "ipv6_listen_ip":      "::1"
    "ipv6_listen_port":    "8080"
    "ipv6_listen_options": "option_value"

Hint

If you set listen_options and ipv6_listen_options to default_server, the corresponding web page becomes the default server and listens to every server name.

GeoIP

To use your GeoIP database with nginx, store the appropriate data files on your server and add the following configuration:

# GeoIP Settings for nginx
nginx::http_cfg_append:
  - "geoip_country  /home/user/geoip/GeoIPv6.dat"
  - "geoip_city /home/user/geoip/GeoLiteCityv6.dat"

# GeoIP related environment variables
environment::variables:
  "GEOIP_ADDR":         "$remote_addr"
  "GEOIP_ADDR":         "$remote_addr"
  "GEOIP_COUNTRY_CODE": "$geoip_country_code"
  "GEOIP_COUNTRY_NAME": "$geoip_country_name"
  "GEOIP_REGION":       "$geoip_region"
  "GEOIP_REGION_NAME":  "$geoip_region_name"
  "GEOIP_CITY":         "$geoip_city"
  "GEOIP_AREA_CODE":    "$geoip_area_code"
  "GEOIP_LATITUDE":     "$geoip_latitude"
  "GEOIP_LONGITUDE":    "$geoip_longitude"
  "GEOIP_POSTAL_CODE":  "$geoip_postal_code"

Hint

for details, see the Module ngx_http_geoip_module documentation

Composer

Every PHP based website type has composer installed and auto updated.

Hint

For details, see the Composer documentation

TYPO3 8

On composer based TYPO3 8 installations, composer runs after every TYPO3 core update, if the following conditions are fulfilled:

  • type: typo3cmsv8
  • ~/composer.json exists
  • ~/composer.json contains typo3/cms

Command used on websites with env: DEV: /usr/local/bin/composer update -n -o typo3/cms Command used on all other environments: /usr/local/bin/composer update --no-dev -n -o typo3/cms

Hint

Composer runs only after changes within the global TYPO3 core in /var/lib/typo3. During deployments, you still have to run composer manually

TYPO3 CMS with Composer

To use TYPO3 CMS 6.x or 7.x with composer, use the following command:

# Export HTTPS PROXY settings to use with get.typo3.org
export HTTPS_PROXY_REQUEST_FULLURI=false

# Download the Base Distribution, the latest "stable" release (6.2)
composer create-project typo3/cms-base-distribution CmsBaseDistribution

# Download the Base Distribution, the "dev" branch (7.x)
composer create-project typo3/cms-base-distribution CmsBaseDistribution dev-master

# Download the Base Distribution, the "dev" 6.2 branch
composer create-project typo3/cms-base-distribution CmsBaseDistribution 6.2.x-dev

TYPO3 Neos with Composer

To use TYPO3 Neos 1.2 with composer, use the following command:

# Create Web/tmp directory, install Neos 1.2 with composer, move to users home directory and cleanup
mkdir ~/Web/tmp/ && cd ~/Web/tmp/ && composer create-project --no-dev typo3/neos-base-distribution TYPO3-Neos-1.2 && rsync -a --delete-after ~/Web/tmp/TYPO3-Neos-1.2/ ~/

Symfony with Composer

To use Symfony 2 with composer, use the following command:

# Create Web/tmp directory, install Symfony2 with composer, move to users home directory and cleanup
mkdir ~/web/tmp/ && cd ~/web/tmp/ && composer create-project symfony/framework-standard-edition symfony && rsync -a --delete-after ~/web/tmp/symfony/ ~/

Monitoring

All sites with "env": "PROD" are monitored 24/7 by default. If you have some sites with frequent outages (e.g. for development purposes), which have to have "env": "PROD" for other reasons, or sites which are not reachable from everywhere due to security reasons, please deactivate monitoring by setting "monitoring": "false".

website::sites:
  "examplenet":
    "type":       "html"
    "env":        "PROD"
    "monitoring": "false"

SSH Access Keys

Hint

To add a SSH key globally for all users, see Key Handling

website::sites:
  "examplenet":
    "ssh_key":
      "contact@example.net":
        "key": "ssh-rsa AAAAB....."

Hint

You can also add more, custom configuration options, see Key Handling for details

Environment Variables

You can set or override environment variables per website by setting the envvar option:

website::sites:
  "examplenet":
    "envvar":
      "MYENVVAR":   "this is the value"
      "DB_HOST":    "override global DB_HOST variable here"
      "http_proxy": "override global http_proxy variable here"