I have recently had to start some software based on a very old PHP version. Since having that directly on the operating system will open it to vulnerabilities, containerization seems to be a good choice.
To accomplish this task we will use a VPS from DigitalOcean, also known as a droplet. We will start with steps to minimally harden the security of the droplet, then use Docker as that is the most popular option for containerization.
Then we will integrate it with our hosting solution based on the popular Virtualmin. We will assume that Virtualmin is already setup on your other server. Some help for that you might find here:
Read more:
- https://www.debian.org/intro/why_debian
- https://www.digitalocean.com/ (with referral link)
- https://www.docker.com/
- https://www.virtualmin.com/
Improve the operating system
We made these steps many times on this site until now. With only slight variations, the steps to follow will be the same. And every good thing starts with an update: 🙂
sudo apt update && sudo apt upgrade -y
Read more:
Looks and performance
Everybody wants a nice way to interact with their command line, which includes pretty fonts and fast response from your server.
zsh
We will start by making the prompt prettier:
sudo apt-get install zsh gitsudo wget -O /usr/local/bin/install_antigen https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/install_antigen.sh && sudo chmod +x /usr/local/bin/install_antigeninstall_antigen- Maybe remember to update
/etc/passwdto usezshas your default shell.
Swap
We will also create a swap file, especially if we are not using SSDs:
sudo fallocate -l 4G /swapfile || dd if=/dev/zero of=/swapfile bs=1024 count=$((4*1024*1024))sudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfileecho "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab
Locales
We also need to clarify the locales:
sudo apt install localessudo dpkg-reconfigure locales
Security
Hardening the security of your server is one of the main concerns and taking the following steps is the minimum thing to do.
sudoer
The first step is to add a sudo user:
adduser sudouserusermod -aG sudo sudouserpasswd -l rootsu - sudouser
Then we will generate our SSH key:
mkdir ~/.sshtouch ~/.ssh/authorized_keyssudo apt update && sudo apt install openssh-clientssh-keygenchmod 700 ~/.sshchmod 400 ~/.ssh/id_rsachmod 600 ~/.ssh/authorized_keys
The next step will be to setup the access via an ssh key:
cat ~/.ssh/id_rsa.pub# on your own system to get your public keynano ~/.ssh/authorized_keys- paste the content of your key and save with
Ctrl+X
Ports
Another thing is to change the ssh port to a different one than the default:
sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_configsudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_configsudo systemctl restart sshd
fail2ban
Fail2ban is an open-source security tool designed to protect servers from brute-force attacks and unauthorized access by monitoring system logs for suspicious activity, such as repeated failed login attempts.
sudo apt-get install fail2ban -ysudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localsudo systemctl start fail2bansudo systemctl enable fail2ban
Firewall
One last thing will be to add the ufw firewall, but first we have to make sure firewalld is removed:
sudo systemctl stop firewalldsudo systemctl disable firewalldsudo apt purge firewalld
Now we add ufw:
sudo apt-get install ufw -ysudo ufw default deny incomingsudo ufw default allow outgoingsudo ufw allow OpenSSH# opens port 22sudo ufw allow in "WWW Full"sudo ufw allow 2222/tcp# use the port set for your ssh serversudo ufw allow from 192.168.0.0/16 to any port 22yes | sudo ufw enable
To remove a rule:
sudo ufw status verbosesudo ufw status numberedsudo ufw delete <number to delete>
Once you do changes, make sure to reload ufw:
sudo ufw reload
Read more:
- https://linuxize.com/post/how-to-list-and-delete-ufw-firewall-rules/
- https://askubuntu.com/questions/115940/how-can-i-setup-ssh-so-that-it-is-restricted-to-my-local-network
Setup containerization
When solving the problem of old web applications, we have multiple choices out of which two stand out.
The first that used to come to mind was to create a VPS with the exact configuration that the application needs. However, this might mean older and unmaintained versions of the operating system which has the dependencies your app has, which open up to big security concerns.
The second and recommended path is to use some containerization solution. You still have to pay attention at how you create your container, as there are many quick downfalls (e.g. using root at your user in your image).
There are multiple ways we can go to create and run the containers: docker, podman, containerd.
Docker installation
The most used choice for containerization is Docker, which is the go to solution when it comes to containerization, often going hand in hand with the docker-compose.
First of all, install the dependencies and add the repository to the OS:
sudo apt install -y ca-certificates curl gnupgsudo install -m 0755 -d /etc/apt/keyringscurl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpgsudo chmod a+r /etc/apt/keyrings/docker.gpgecho \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Then we can actually install docker and docker-compose and add ourselves to the docker group, so that we won't have to use sudo. Make sure you remember to logout and login again.
sudo apt updatesudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-pluginsudo systemctl enable --now dockersudo usermod -aG docker $USERsudo apt install -y docker-compose-plugin
We can remove docker with the following commands:
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-pluginsudo rm -rf /var/lib/dockersudo rm -rf /etc/apt/keyrings/docker.gpgsudo rm /etc/apt/sources.list.d/docker.list
More info:
Firewall rules
This might not be obvious, but by default docker overrides the rules enforced by ufw.
We can fix this situation by doing the following:
sudo nano /etc/docker/daemon.json- add the following json and save:
{ "iptables": false } sudo systemctl restart docker
Be aware that this solution will isolate docker from the outside world. Among others, this means that you won't be able so solve DNS inside a Dockerfile and thus access outside resources (e.g. run the apt update command successfully).
Host the application
As mentioned in the title, we are setting up an old php application on its own server. We could open the port directly, but that leads to some complications. On the one hand, we would need to handle certificates and on the other hand we would need to invest more time in ensuring data safety.
For our situation, we will use the mysql database on a virtual host in virtualmin, hosted on a different server. Obviously, we will use git and secrets and other tools which are now popular in an application's lifecycle.
One important security consideration is that we will use the internal IP addresses provided by DigitalOcean. This way, even if we open some ports, the attacker will have to be inside our network to be able to exploit possible vulnerabilities.
Webmin, Virtualmin and Apache are dealt with on the Virtualmin. Docker is the main reference on the VPS we have created for the old application.
Webmin
Webmin goes hand in hand with Virtualmin. While the latter is specially designed to deal with hosting websites, the former deals with your base operating system.
What we need to do here is add some modules to Apache:
- Navigate to
Webmin > Servers > Apache Webserver - Go to
Configure Apache Modules, and enable the following modules (if not already):proxy,proxy_http,proxy_connect,proxy_ajp(optional), then clickEnable Selected Modules - Restart Apache:
sudo systemctl restart apache2
More info:
Virtualmin
Virtualmin will be responsible for acting like a proxy to the VPS containing the older PHP 7 version. Assuming our host is already created, we need to go to Virtualmin > Your host > Services > Configure Website > Edit Directives and apply the following rules:
ProxyPreserveHost On
ProxyPass / http://192.168.1.100:81/
ProxyPassReverse / http://192.168.1.100:81/Code language: Apache (apache)
Make sure it’s inside the <VirtualHost _:80> block (or \_:443 for HTTPS), or at root level if the VirtualHost tag is not visible.
Obviously, we assume the PHP 7 host has the internal ip of 192.168.1.100 and it runs on port 81.
Mysql
For storing the data, we have multiple choices we will use the virtualmin database via a port connection. An interesting alternative is using a SSH tunnel between the two servers.
Setup the account with access (remember to let `'` where they are in the mysql commands):
mysql -u root -p <root_password>CREATE USER '<restricted_user>'@'<allowed_ip>' IDENTIFIED BY '<strong_password>';GRANT ALL PRIVILEGES ON <database_name>.* TO '<restricted_user>'@'<allowed_ip>';FLUSH PRIVILEGES;
Make sure you remember to bind mysql to 0.0.0.0 instead of the default 127.0.0.1. Also, be sure to check that the user you created actually has access to the database in the virtualmin virtual server.
Also, remember to check that the user has the proper permissions:
SELECT * FROM mysql.user WHERE User = '<restricted_user>' AND Host = '<allowed_ip>';SELECT * FROM mysql.db WHERE User = '<restricted_user>' AND Host = '<allowed_ip>' AND Db = '<database_name>';SHOW GRANTS FOR '<restricted_user>'@'<allowed_ip>';
More information:
- https://www.baeldung.com/linux/docker-container-access-host-ssh-tunnel
- https://stackoverflow.com/questions/39143689/access-hosts-ssh-tunnel-from-docker-container
ufw
In case you have ufw enabled on your server as a firewall, you need to change some rules for both servers.
On the old app server, we need to open the port of the app (e.g. 8080) to the internal ip of the other server:
sudo ufw allow from <virtualmin_server_ip> to any port 8080 proto tcp
On the Virtualmin server we need to allow access to the mysql port:
sudo ufw allow from <old_application_server_ip> to any port 3306 proto tcp
Read more:
Final thoughts
Such an implementation will help with your needs, but the best solution is obviously updating your application to be able to use the latest versions of its dependencies. Or removing it from your apps, in case it's not important enough.

