Installing the ModX content management system on OpenResty
published onWhilst you will find another article on this site about how to install ModX (Installing the MODX content management system) I decided to write this one for a few reasons.
First, whilst the differences between installing NGinx and OpenResty are fairly minor it is always easier when you can find something online which meets your needs more closely than alternatives using slightly different commands, file paths etc.
Second, this article includes a few minor changes and tries to further simplify the process for you.
Why ModX?
The ModX content management system gives you more flexibility to set up a site the way you want. You don't need to worry about having a theme that only allows you to make certain changes.
Adding content is also very easy to do and uses a powerful backend to power your unique web sites.
Whilst some other content management systems add a lot of bloated code to your site ModX doesn't do this. As a result your web site won't take as long to load as other systems and will run quickly and smoothly.
Let's get started installing ModX on OpenResty!
First, the intial setup
Run sudo apt install lsb-release ca-certificates apt-transport-https software-properties-common language-pack-en-base unzip at the command prompt to get some inital software installed.
Now we will install PHP
Run the following set of commands to get PHP installed before moving on to the configuration stage
sudo add-apt-repository ppa:ondrej/phpsudo apt updatesudo apt install php7.4-{common, cli, bz2, zip, zlib, curl, intl, mysql, snmp, memcached, imagick, gd, fileinfo, imap, ldap, soap, tidy, xml, simplexml, json, gmp, pspell, mbstring, opcache, fpm, ssh2, imap, redis, apcu, mcrypt, pdo, openssl}sudo apt install ghostscript imagemagick
Next we configure PHP
First edit www.conf by running sudo nano /etc/php/7.4/fpm/pool.d/www.conf at the command line. Make sure these lines in the file look like the ones below.
listen = /run/php/php7.4-fpm.socklisten.owner = www-datalisten.mode = 0660
Now edit the php.ini with sudo nano /etc/php/7.4/fpm/php.ini and make the following changes
output_buffering = Offexpose_php = offmax_execution_time = 60max_input_time = 60max_input_vars = 1000memory_limit = 512Mdisplay_errors = Offdisplay_startup_errors = Offlog_errors = Onhtml_errors = Offpost_max_size = 8Mupload_max_filesize = 2Mallow_url_fopen = Offdate.timezone = Europe/Londonopcache.enable=1opcache.memory_consumption=128opcache.max_accelerated_files=10000opcache.revalidate_freq=200opcache.save_comments=1cgi.fix_pathinfo=0extension=php_opensslextension=fileinfoextension=pdo_mysqlzlib.output_compression Onzlib.output_compression_level 5
Once done make sure to restart PHP by running sudo systemctl restart php7.4-fpm.
Let's install MariaDB and set up a database
Enter sudo apt-get install mariadb-server to install MariaDB and enter sudo mysql_secure_installation to secure your new installation.
Now let us make sure that MariaDB will only listen to tthe localhost - making sure that remote computers cannot connect - by running sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf and making sure the following two lines are present
bind-address = 127.0.0.1local-infile = 0
If either of these commands are not in the file make sure to add them next to each other.
Once this is done it is time to create the database that will be used later on when setting up ModX. Run sudo mysql -u root -p and type the following commands at the MariaDB prompt.
CREATE DATABASE modxdb CHARACTER SET utf8mb COLLATE utf8_general_ci;CREATE USER 'modx'@'localhost' IDENTIFIED BY 'password';GRANT ALL PRIVILEGES ON modxdb.* TO 'modx'@'localhost';FLUSH PRIVILEGES;exit
It is time to install OpenResty
Installing OpenResty takes a few commands. You may find it easier to copy and paste these as some are rather long.
cd ~sudo apt install wget gnupg ca-certificateswget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.listsudo apt updatesudo apt install openresty openresty-resty openresty-restydoc openresty-opm openresty-zlib openresty-pcre luarockssudo systemctl enable openrestysudo systemctl start openresty
Setting up OpenResty
Again - there are a lot of commands here - but you will have completed the initial setting up of OpenResty in no time.
Run the following commands
sudo mv /usr/local/openresty/nginx/conf/nginx.{conf,original}sudo nano /usr/local/openresty/nginx/conf/nginx.conf
In this file add the following contents
user www-data;worker_processes auto;pid /usr/local/openresty/nginx/logs/nginx.pid;events { worker_connections 1024; use epoll; epoll_events 512; multi_accept on;}http { server_tokens off; # change my_server to whatever text you wish to use # this can also go in the server block if you wish to change this per site #more_set_headers "Server: my_server"; include mime.types; default_type application/octet-stream; charset utf-8; charset_types text/css text/plain text/vnd.wap.wml text/javascript text/markdown text/calendar text/x-component text/vcard text/cache-manifest text/vtt application/json application/manifest+json; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/openresty/access.log combined; error_log /var/log/openresty/error.log warn; client_body_buffer_size 16K; client_header_buffer_size 1m; client_max_body_size 8m; large_client_header_buffers 4 8k; sendfile on; tcp_nopush on; tcp_nodelay on; gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_buffers 16 8k; gzip_min_length 256; gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/vnd.ms-fontobject application/wasm application/x-web-app-manifest+json application/xhtml+xml application/xml font/eot font/otf font/ttf image/bmp image/svg+xml text/cache-manifest text/calendar text/css text/javascript text/markdown text/plain text/xml text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; gzip_disable "MSIE [1-6]\."; reset_timedout_connection on; keepalive_timeout 20s; keepalive_requests 30; client_header_timeout 10; client_body_timeout 10; send_timeout 10s; # aio on; directio 4m; directio_alignment 512; open_file_cache max=1000 inactive=30s; open_file_cache_valid 30s; open_file_cache_min_uses 4; open_file_cache_errors on; # Limits limit_req_log_level warn; limit_req_zone $binary_remote_addr zone=reqlimit:10m rate=10r/m; limit_conn_zone $binary_remote_addr zone=connlimit:100m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on;include ../sites/*;
}
Now run the following commands to continue the configuration process
sudo mkdir /usr/local/openresty/nginx/sitessudo mkdir /var/log/openrestysudo echo 'fastcgi_param HTTP_PROXY "";' | sudo tee -a /usr/local/openresty/nginx/conf/fastcgi.confsudo echo 'fastcgi_param HTTP_PROXY "";' | sudo tee -a /usr/local/openresty/nginx/conf/fastcgi_paramssudo nano /usr/local/openresty/nginx/sites/default.conf
Make sure to paste the following code into the open file
server { # Listen on port 80. listen 80 default_server; listen [::]:80 default_server; # The document root. root /usr/local/openresty/nginx/html/default; # Add index.php if you are using PHP. index index.html index.htm; # The server name, which isn't relevant in this case, because we only have one. server_name _; # When we try to access this site... location / { # ... first attempt to serve request as file, then as a directory, # then fall back to displaying a 404. try_files $uri $uri/ =404; } # Redirect server error pages to the static page /50x.html. error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/local/openresty/nginx/html; }}
Continuing on with the setup run the following two commands
sudo mkdir /usr/local/openresty/nginx/html/defaultsudo nano /usr/local/openresty/nginx/html/default/index.html
and add the following line into the blank editor window
<html><head></head><body</body></html>
We are now getting a bit closer to finishing setting up OpenResty. We need to create another file by running sudo nano /usr/local/openresty/nginx/sites/[domain].conf (where [domain] is your own domain name and adding the following code
server { listen 80; listen [::]:80; server_name [domain].com www.[domain].com; root /usr/local/openresty/nginx/html/[domain];}
Lastly run the following set of commands which will get OpenResty up and running ready for us to install our copy of ModX.
sudo mkdir /usr/local/openresty/nginx/html/[domain]sudo chown -R www-data:www-data /usr/local/openresty/nginx/html/[domain]sudo chmod -R 750 /usr/local/openresty/nginx/html/[domain] sudo systemctl reload openresty
It is time to secure our domain
It is always a good idea to set up a SSL / TLS certificate for your site. By running the following commands you will have one set up in no time.
sudo apt-get install snapdsudo snap install core; sudo snap refresh coresudo snap install --classic certbotsudo ln -s /snap/bin/certbot /usr/bin/certbot sudo certbot certonly --webroot -w /usr/local/openresty/nginx/html/[domain] -d [domain] -d www.[domain]
Make sure to make a note of where your certificates have been saved.
Updating the ModX server block
The initial server block, telling OpenResty where to find your site was only a basic stub in order to get a working SSL / TLS certificate. We now need to replace this with the actual server block we want to use with our ModX installation.
To do this run the following two commands and enter the code below into the blank file
sudo rm /usr/local/openresty/nginx/sites/[domain].confsudo nano /usr/local/openresty/nginx/sites/[domain].conf
#this will redirect any non-https requests to https://wwwserver { listen 80; listen [::]:80; server_name [domain] www.[domain]; # Allow access to the ACME Challenge for Let's Encrypt <- <3 location ~ /\.well-known\/acme-challenge { allow all; } return 301 https://www.[domain]$request_uri permanent;}#this wil redirect any https://... to https://www...server { listen 443; listen [::]:443; server_name [domain]; ssl_certificate /etc/letsencrypt/live/[domain]/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/[domain]/privkey.pem; return 301 https://www.[domain]$request_uri permanent;}#this is the https://www... configurationserver { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.[domain]; root /usr/local/openresty/nginx/html/[domain]; index index.php; access_log /var/log/openresty/[domain]_access.log combined; error_log /var/log/openresty/[domain]_error.log warn; charset utf-8; source_charset utf-8; ssl_certificate /etc/letsencrypt/live/[domain]/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/[domain]/privkey.pem; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_stapling on; ssl_stapling_verify on; # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam # or just simply openssl dhparam 2048 > /path/to/dhparam ssl_dhparam /path/to/dhparam; location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } # Httproxy vulnerability proxy_set_header Proxy ""; # Request headers for overwriting proxy_set_header X-Original-URL ""; proxy_set_header X-Rewrite-URL ""; proxy_set_header X-Host ""; proxy_set_header X-Forwarded-Server ""; proxy_set_header X-HTTP-Host-Override ""; proxy_set_header Forwarded ""; # Prevent Information leaks proxy_hide_header X-Powered-By; proxy_hide_header Server; proxy_hide_header X-AspNetMvc-Version; proxy_hide_header X-AspNet-Version; # http://blog.portswigger.net/2017/07/cracking-lens-targeting-https-hidden.html proxy_set_header clientIPAddress ""; proxy_set_header x-forwarded-for ""; proxy_set_header client-ip ""; proxy_set_header forwarded ""; proxy_set_header from ""; proxy_set_header referer ""; proxy_set_header x-client-ip ""; proxy_set_header x-originating-ip ""; proxy_set_header x-wap-profile ""; # http security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy strict-origin-when-cross-origin; add_header X-Permitted-Cross-Domain-Policies none; # Add Security cookie flags proxy_cookie_path ~(.*) "$1; SameSite=strict; secure; httponly"; add_header Content-Security-Policy "base-uri https://www.[domain]; default-src https:; script-src https: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; child-src https://www.[domain]"; add_header Permissions-Policy "accelerometer=(self), ambient-light-sensor=(self), autoplay=(self), battery=(self), camera=(self), cross-origin-isolated=(self), display-capture=(self), document-domain=(self), encrypted-media=(self), execution-while-not-rendered=(self), execution-while-out-of-viewport=(self), fullscreen=(self), geolocation=(self), gyroscope=(self), keyboard-map=(self), magnetometer=(self), microphone=(self), midi=(self), navigation-override=(self), payment=(self), picture-in-picture=(self), publickey-credentials-get=(self), screen-wake-lock=(self), sync-xhr=(self), usb=(self), web-share=(self), xr-spatial-tracking=(self)"; # only add this line once you are sure everything works #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;" always; # add_header Allow "GET, POST, HEAD"; if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; } # Allow access to the ACME Challenge for Let's Encrypt <- <3 location ~ /\.well-known\/acme-challenge { allow all; } error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 500 501 502 503 504 505 506 507 508 509 510 511 /error.html; location /error.html { internal; } limit_conn connlimit 1000; # Simultaneous Connections # the MODX part location @modx-rewrite { rewrite ^/(.*)$ /index.php?q=$1&$args last; } # only enable this caching section once your site is live # 1y might be too long and 7d, 1w etc might be better #location ~* \.(?:ico|css|js|jpe?g|png|gif|svg|pdf|mov|mp4|mp3|woff|woff2|otf|ttf)$ { # expires 1y; # add_header Pragma public; # add_header Cache-Control "public"; # gzip_vary on; #} # add caching to a particular directory #location /assets { # this adds an Expires header and a Cache-Control header #expires 1y; #} location = /robots.txt { try_files $uri $uri/ @modx-rewrite; } location /autodiscover/autodiscover.xml { return 404; } # block PHP execution from the listed directory #location ~ /assets/uploads/.*\.php$ { # return 403; #} location / { try_files $uri $uri/ @modx-rewrite; } # protect parts of MODX location ~ ^/(\.(?!well_known)|_build|_gitify|_backup|core|config.core.php) { rewrite ^/(\.(?!well_known)|_build|_gitify|_backup|core|config.core.php) /index.php?q=doesnotexist; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(.*)$; fastcgi_pass unix:/run/php/php7.4-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include /usr/local/openresty/nginx/conf/fastcgi_params; fastcgi_ignore_client_abort on; fastcgi_param SERVER_NAME $http_host; }}
That is a lot of text to copy! This is set up to make sure that all requests for your domain name go to https://www.[domain]. If that isn't what you want then feel free to make the necessary changes to your server block.
The block above also contains a lot of instructions to improve the security of your site. You will also notice the following
- There is a reference to
dhparamwhich you need to take care of - The
Strict-Transport-Securityshould only be uncommented once you know that your site will only ever be accessed over https. - The cache instructions shown above are all commented out. It is best to leave them this way until you have finished editing your site and it is live, or, ready for the public to view.
Once you have managed to get through this file and made all the changes you need to make sure to run sudo systemctl restart openresty to activate your new server block.
Now we can install ModX
By running the following commands you will download the latest version of ModX (at the time of writing), uncompress it, copy all the files you need into the webroot, set up any extra files or permissions and clean up after yourself.
cd ~wget https://modx.com/download/direct/modx-3.0.1-pl.zipunzip modx-3.0.1-pl.zipcd modx-3.0.1-plsudo mkdir /usr/local/openresty/nginx/html/[domain]sudo cp -r * /usr/local/openresty/nginx/html/[domain]cd ..rm -rf modx-3.0.1-pl modx-3.0.1-pl.zipsudo touch /usr/local/openresty/nginx/html/[domain]/error.htmlsudo touch /usr/local/openresty/nginx/html/[domain]/core/config/config.inc.phpsudo chown -R www-data:www-data /usr/local/openresty/nginx/html/[domain]sudo chmod -R 755 /usr/local/openresty/nginx/html/[domain]
Now to finally setup Modx
Visit [domain]/setup in your browser and follow the installation procedure. At the end make sure that the option to delete the setup folder is ticked.
Congratulations!
You now have a fully working version of ModX set up using OpenResty and just waiting for you to set your creative juices flowing.
There is a lot to learn yet and you can find some initial instructions at https://docs.modx.com.
I hope to be bringing you more ModX articles in the future as I learn and develop my skills.
