Dev
Nextcloud with Docker and NGINX on Ubuntu 22.04
There are a ton of cloud storage providers out there including the large players: Dropbox, OneDrive, Apple Cloud and so on. Though some of these have pretty good security, the files in those storages are still accessible to the admins of said services – and in extension to governments and other third party actors.
Do I have anything to hide? No. Do I want the government or random hackers and script kiddies looking at my personal picture archives? No again. So a self-hosted solution is the way to go.
Enter Nextcloud, an ownCloud fork. I decided to run the Docker version behind an NGINX proxy server.
It’s pretty simple this time, let’s jump into it!
Step 1 – Install Docker and docker-compose
We will use
sudo,aptandapt-getfor this. All these tools usually come with Ubuntu, so no need to install them. Let’s grab us some nice Docker stuff.# Make sure everything is up to date sudo apt update # Get stuff sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common # Get ourselves some sweet gpg keys for docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # Add the Docker repository sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # Get Docker sudo apt update && sudo apt-get install docker-ce docker-ce-cli containerd.io # Get docker-compose (you may need to adjust the version number, 'v2.24.5') … sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # … and make it writeable sudo chmod +x /usr/local/bin/docker-composeIn addition, you might need to add the server user for the website to the Docker group (and create a docker group, if not in place already):
sudo groupadd docker sudo usermod -aG docker usernameobviouslyuseyourwebsiteusernamehereThat’s all the installation stuff server-wise. Next, let’s set up NGINX.
Step 2 – Set up NGINX
Let’s create a configuration file for the webpage (or add a new server to an existing configuration file).
/etc/nginx/sites-available/example.comserver { server_name cloud.example.com; # Website URL root "/var/www/example.com/cloud.example.com"; # Website Folder location / { proxy_pass http://localhost:3000/; # Port as set in docker-compose (we'll do this later) proxy_set_header Host $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-Proto $scheme; client_max_body_size 0; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; } location = /.well-known/carddav { return 301 $scheme://$host/remote.php/dav; } location = /.well-known/caldav { return 301 $scheme://$host/remote.php/dav; } listen 80; }At this point you’d typically make sure to add a certificate, e. g. utilising
certbot --nginxto do so. I won’t cover that here.Step 3 – Add docker-compose.yml and db.env
Let’s start with the
docker-compose.ymlfile. You can copy it from here, just make sure to use a free port. 30xx should be fine. Usenetstat -tunlep | grep LISTEN | awk '{print $4}'to make sure.version: '3' services: db: image: mariadb command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW restart: always volumes: - ./db:/var/lib/mysql env_file: - db.env redis: image: redis restart: always app: image: nextcloud:latest build: ./app restart: always ports: - 127.0.0.1:3000:80 # remember the port (3000) in the nginx file? # Make sure this one is the same. And it's not in use, of course. volumes: - ./nextcloud:/var/www/html environment: - MYSQL_HOST=db env_file: - db.env depends_on: - db - redis cron: build: ./app restart: always volumes: - ./nextcloud:/var/www/html entrypoint: /cron.sh depends_on: - db - redis volumes: db: nextcloud:Save and add another file, this time
db.env:db.envMYSQL_ROOT_PASSWORD=UseSomeCrypticPasswordHere MYSQL_PASSWORD=UseAnotherCrypticPasswordHere MYSQL_DATABASE=nextcloud MYSQL_USER=nextcloudStep 4 – Add the Dockerfile and the redis configuration file
Create a folder named
appin your site directory, and add two files to it.mkdir app cd app touch Dockerfile touch redis.conf.phpAdd some content to those files.
app/DockerfileFROM nextcloud:apache COPY redis.config.php /usr/src/nextcloud/config/redis.config.phpapp/redis.config.php<?php $CONFIG = array ( 'memcache.local' => '\\OC\\Memcache\\Redis', 'memcache.locking' => '\\OC\\Memcache\\Redis', 'filelocking.enabled' => 'true', 'redis' => array( 'host' => 'redis', 'port' => 6379, ), );Step 5 – Cross your fingers and …
… run
docker-compose.docker-compose upCheck the output for errors. If you don’t see any, terminate the containers with Ctrl-C (once!).
Step 6 – Fire up a detached container
Run docker-compose again, so it starts some detached containers. Head to your website, and you should be able to follow the frontend installation process.
docker-compose up -dIf everything went well, you should have a running Nextcloud instance. If not, let me know and I might add more to this tutorial.
WordPress with NGINX, PHP-FPM and MariaDB (for MySQL) on Ubuntu 22.04
There are a lot of ways to run WordPress. Here is the one I chose, maybe it helps you as well. If you have any questions, don’t hesitate to leave them in the comments. I will try my best to help you out. I am assuming you install this on your own server and have root access, or at least sudo access with your account.
That being said: Let’s jump into it!
- Step 1 – Install packages
- Step 2 – Set up the database
- Step 3 – Set up PHP-FPM
- Step 4 – Install WordPress
- Step 5 – Configure WordPress
- Step 6 – Set up NGINX
Step 1 – Install packages
Before you start installing and configuring WordPress, you need to make sure you have all the tools you need. Use apt to install all the needed packages:
sudo apt install software-properties-common sudo add-apt-repository -y ppa:ondrej/php sudo apt update sudo apt install nginx sudo apt install php-fpm sudo apt install mariadb-serverStep 2 – Set up the database
The next step is setting up the database. This should be done rather quickly. All you need to do is login to mysql.
sudo systemctl status mariadb # check if mariadb is up and running sudo mariadbIf you have set a password for mysql, use the interactive mode instead.
sudo systemctl status mariadb # check if mariadb is up and running sudo mariadb -u root -p“Inside” we need to create a database, a user and then grant the needed privileges for the database to the generated user.
mariadbCREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'user'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON wordpress.* TO 'user'@'localhost'; QUIT;You can change the database name, user and password as you like. Make sure to jot them down, you will need them later.
Step 3 – Set up PHP-FPM
For this you will use PHP-FPM to handle everything php, passed on by NGINX. This utilizes a pool, for that we need to create a file in
/etc/php/8.2/fpm/pool.d. Replace “8.2” with the version of php that you are using.php -v # find out which php-version you are using, I will use 8.2 here sudo vim /etc/php/8.2/fpm/pool.d/example.com.conf # replace <example.com> with your domain or anything you likeIn that file is the configuration for php, if used through the FastCGI Process Manager (FPM). Here is an example configuration. Remember to activate the vim insert mode by typing
i, before you paste it in./etc/php/8.2/fpm/pool.d/example.com.conf[examplecom] ; change to a unique name ; replace with the user and group of your php files: user = example_com group = example_com ; replace example-com with your domain listen = /run/php/example-com-fpm.sock ; should be user and group of your web server, probably leave it be: listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 120 pm.start_servers = 12 pm.min_spare_servers = 6 pm.max_spare_servers = 18 clear_env = no php_admin_value[opcache.save_comments] = 1 php_admin_value[opcache.interned_strings_buffer] = 16 php_admin_value[opcache.revalidate_freq] = 60 php_admin_value[opcache.validate_timestamps] = 0 env[HOSTNAME] = $HOSTNAME env[PATH] = /usr/local/bin:/usr/bin:/bin env[TMP] = /tmp env[TMPDIR] = /tmp env[TEMP] = /tmp ; you can use your domain here as well php_admin_value[error_log] = /var/log/fpm-php.example-com.log php_admin_flag[log_errors] = on php_admin_value[memory_limit] = 1GThat should do it. Write to the file and exit vim by typing
:wq. After that, restart PHP-FPM.sudo systemctl restart php8.2-fpmStep 4 – Install WordPress
Change into your web root folder and create a folder for your page. Change path and folder name as needed. If you do not know how to do this, you probably should not have root access, tbh. … 😬
mkdir /var/www/html/example.com cd /var/www/html/example.comIn that folder, download wordpress and untar it.
wget https://wordpress.org/latest.tar.gz tar --strip-components=1 -xvzf latest.tar.gz rm latest.tar.gzNote that
--strip-components=1strips the first part of the files’ paths, in this case the folderwordpressit would normally extract into.That should do it for now, next we need to configure your WordPress.
Step 5 – Configure WordPress
We will do this by editing the wp-
config.phpwith our editor of choice, which of course is vim.cp wp-config-sample.php wp-config.php vim wp-config.phpWe’ll make it a short one, of course you can keep all the comments in there. If you want to put in more stuff, you can find a generator at generatewp.com.
Link for the salt: api.wordpress.org
wp-config.php<?php /* MySQL settings */ define( 'DB_NAME', 'wordpress' ); // database from step 2 define( 'DB_USER', 'user' ); // user from step 2 define( 'DB_PASSWORD', 'password' ); // password from step 2 define( 'DB_HOST', 'localhost' ); define( 'DB_CHARSET', 'utf8mb4' ); /* MySQL database table prefix. */ $table_prefix = 'wp_'; /* Authentication Unique Keys and Salts. */ /* head to https://api.wordpress.org/secret-key/1.1/salt/ and copy it here */ define('AUTH_KEY', 'I$RCe|Avd6q<^q~}r@!mwq^ds-f+{po'); define('SECURE_AUTH_KEY', '3.}i|OH|UEG3Ap|p#bJ+P{P%4N-WP)^'); define('LOGGED_IN_KEY', '0(a gw|-/d7z3T=yN)fh?WD,sWCjDD%'); define('NONCE_KEY', 'm}(s:T%xu]gwKgwuHcThe_TEY9l7v_?'); define('AUTH_SALT', 'JrRb)}~KaZcL5P?v|)h{m$(:m!wb9EU'); define('SECURE_AUTH_SALT', 'enY94*k/e80(F]]_+Z}4as|B&+KX)b1'); define('LOGGED_IN_SALT', '=;ir6WRQj,ahn=5$? /h6|~&%0QWg=q'); define('NONCE_SALT', 'c&hjS7e9xe{x*ip=7LoYMO@|t4d`w5B'); /* Absolute path to the WordPress directory. */ if ( !defined('ABSPATH') ) define('ABSPATH', dirname(__FILE__) . '/'); /* Sets up WordPress vars and included files. */ require_once(ABSPATH . 'wp-settings.php');Aaaand with that done, all we finally need to do is configure NGINX to do what it’s supposed to do.
Step 6 – Set up nginx
In order to do that, we are going to create a file in the
sites-availablefolder of NGINX.sudo vim /etc/nginx/sites-available/example.com/etc/nginx/sites-available/example.comserver { listen 80; root /var/www/html/example.com; # replace example.com index index.php index.html index.htm; server_name example.com; # replace example.com client_max_body_size 500M; location / { try_files $uri $uri/ /index.php?$args; } location = /favicon.ico { log_not_found off; access_log off; } location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { expires max; log_not_found off; } location = /robots.txt { allow all; log_not_found off; access_log off; } location ~ \.php$ { include snippets/fastcgi-php.conf; # replace example-com-fpm with socket from step 3 fastcgi_pass unix:/var/run/php/example-com-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }Save it with
:wq, link theexample.comfile from/etc/nginx/sites-enabled, test and restart NGINX.sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl restart nginxThat’s it. Head to
example.comand finish the installation from the web installer. Happy blogging!Edit: You still need to install a Certificate for the website, but that's not part of this post. If you need any help with that, let me know and I will add a tutorial for Let's Encrypt.Batch change permissions for folders and files
If you ever need to change permissions for a bunch of folder and/or files without a GUI, you can utilize find and xargs to achieve that in the shell in a flash:
find -xtype d | xargs chmod 755 # directories, standard permissions find -xtype f | xargs chmod 644 # files, standard permissions find -xtype d | xargs chmod 775 # directories, writeable for group find -xtype f | xargs chmod 664 # files, writeable for groupBasically what we are doing here is using
findto find every directory or file, depending on the type. The optionxtype, followed bydorf, helps us limit the (recursive) search to directories and files respectively.xtypeincludes symbolic links into the results, whiletypewould not. the resulting directory/file names are then piped intoxargs, which applies the command it is followed by (here:chmod 755andchmod 644) to the list it receives from the pipe.#1476107295 TYPO3\CMS\Core\Error\Exception
I ran into a a small problem after activating the TYPO3 debug mode in my ddev TYPO3 development installation. When opening the Configuration Presets again, I was greeted with the following error:

Something went wrong Please use Check for broken extensions to see if a loaded extension breaks this part of the install tool and unload it. The box below may additionally reveal further details on what went wrong depending on your debug settings. It may help to temporarily switch to debug mode using Settings > Configuration Presets > Debug settings. If this error happens at an early state and no full exception back trace is shown, it may also help to manually increase debugging output in %config-dir%/system/settings.php:['BE']['debug'] => true, ['SYS']['devIPmask'] => '*', ['SYS']['displayErrors'] => 1,['SYS']['exceptionalErrors'] => 12290Upon investigating the Ajax error shown below the message I saw it was throwing an exception:
/typo3/module/tools/settings(1/1) #1476107295 TYPO3\CMS\Core\Error\Exception PHP Runtime Deprecation Notice: str_contains(): Passing null to parameter #1 ($haystack) of type string is deprecated in /var/www/html/vendor/typo3/cms-install/Classes/Configuration/Image/ImageMagick6Preset.php line 85Instead of increasing the debugging output as suggested, I guessed that – short of fixing the underlying function – deactivating deprecation notices would do the trick for now, hoping that the TYPO3 devs will fix this in an upcoming release. So I jumped into the console, navigated to
/config/systemand edited the fileadditional.php, and found the following lines:/config/system/additional.php'SYS' => [ 'trustedHostsPattern' => '.*.*', 'devIPmask' => '*', 'displayErrors' => 1, ],A little addition, and everything worked out fine:
/config/system/additional.php'SYS' => [ 'trustedHostsPattern' => '.*.*', 'devIPmask' => '*', 'displayErrors' => 1, 'exceptionalErrors' => 8191 ],I might need to adjust this, if other errors show up, but for now it’s fine. On the downside I will not catch any deprecated stuff for now, but I can live with that.
Documentation used: