For managing multiple websites without much traffic, we can create our own hosting solution. I have previously written on this topic, but as time passes, technologies evolve and the knowledge to be shared enriches. Thus, it’s time for a new iteration, with emphasis on security and reducing the attack surface.
I have previously performed similar tasks and described them in other articles:
- https://draghici.net/2020/05/02/virtualmin-hosting-on-debian-10-buster/
- https://draghici.net/2019/01/25/hosting-websites-on-my-own-vps/
We will use a DigitalOcean VPS and use as previously their recommendations they provide for setting up a server:
The code for the scripts has been published on this repository:
Create a sudo user
Probably most Linux users, regardless of their level, know that it’s a bad practice to use the root
user for regular day to day operations. This is why we will start by setting up a sudo
user. In our commands we will assume the user is sudouser
, but maybe this is not the best name to use in real life 🙂
adduser sudouser
usermod -aG sudo sudouser
passwd -l root
su - sudouser
Setup the ssh private key
Assuming you already have a ssh
key generated on your operating system, the next step will be to assign the public key to your newly created user.
On your own system, read the public key, which we will later add to the authorized keys of the server. The key is usually stored in the .ssh
folder of your user and you can use multiple commands to get it or manually open the file. One such command is:
cat ~/.ssh/id_rsa.pub
On the server, run these commands (they are basic mainly to make what is happening clear):
mkdir ~/.ssh
touch ~/.ssh/authorized_keys
nano ~/.ssh/authorized_keys
- paste the content of your key and save with
Ctrl+X
chmod 700 ~/.ssh
chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/authorized_keys
More information:
- https://www.digitalocean.com/community/tutorials/how-to-create-ssh-keys-with-openssh-on-macos-or-linux
- https://superuser.com/questions/215504/permissions-on-private-key-in-ssh-folder
A script to create a new sudoer
We can automate this process with a bash script:
#!/bin/bash
# Check if the script is run as root
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" 1>&2
exit 1
fi
# Prompt for the new username
read -p "Enter the new sudo user's name: " USERNAME
# Create a new user and add to sudo group
adduser --gecos "" $USERNAME
usermod -aG sudo $USERNAME
# Lock the root account
passwd -l root
# Setup SSH key for the new user
USER_HOME=$(eval echo ~$USERNAME)
mkdir -p $USER_HOME/.ssh
touch $USER_HOME/.ssh/authorized_keys
echo "Please paste the public SSH key. To retrieve it from your system, you can use the terminal command: 'cat ~/.ssh/id_rsa.pub'"
read SSH_KEY
# Check if the key already exists in the authorized_keys
if grep -qsF "$SSH_KEY" $USER_HOME/.ssh/authorized_keys; then
echo "Key already exists in authorized_keys."
else
echo "$SSH_KEY" >> $USER_HOME/.ssh/authorized_keys
# Set permissions
chown -R $USERNAME:$USERNAME $USER_HOME/.ssh
chmod 700 $USER_HOME/.ssh
chmod 600 $USER_HOME/.ssh/authorized_keys
echo "SSH key added."
fi
echo "User $USERNAME created and configured."
Code language: PHP (php)
You can quickly run this script on your system with:
wget -q -O - https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/add_sudoer_with_ssh.sh | sudo bash
Setup zsh
A nicer way than bash
to interact with your system is zsh
. We will install it globally and then make use of it for each of the users who want it. To install it on your system, run:
sudo apt-get install zsh
We will then create a script to enable antigen for any user who might want to use it with zsh, with the following content:
#!/bin/bash
# Script to install Zsh and Antigen for the current user
# Check the required utils
REQUIRED_UTILS=("git" "zsh")
for UTIL in "${REQUIRED_UTILS[@]}"; do
if ! command -v $UTIL &> /dev/null; then
echo "The required utility $UTIL is not installed. Please install it and rerun this script."
exit
fi
done
# Check if the script is run by a regular user, not root
if [ "$EUID" -eq 0 ]; then
echo "Please run as a regular user, not as root"
exit
fi
# Define Antigen installation path
ANTIGEN_PATH="$HOME/.antigen"
# Check if Antigen is already installed
if [ -d "$ANTIGEN_PATH" ]; then
echo "Antigen is already installed for user $(whoami)."
exit
fi
# Create Antigen directory
mkdir -p "$ANTIGEN_PATH"
# Download Antigen
echo "Downloading Antigen..."
curl -L git.io/antigen > "$ANTIGEN_PATH/antigen.zsh"
# Update .zshrc with Antigen configuration
{
echo "# Antigen configuration"
echo "source $ANTIGEN_PATH/antigen.zsh"
echo "antigen use oh-my-zsh"
echo "antigen bundle git"
echo "antigen bundle command-not-found"
echo "antigen bundle zsh-users/zsh-syntax-highlighting"
echo "antigen bundle zsh-users/zsh-autosuggestions"
echo "antigen theme bira"
echo "antigen apply"
} >> "$HOME/.zshrc"
# Check if current shell is Zsh
if [[ "$SHELL" == *"/zsh" ]]; then
echo "Antigen has been installed and configured for user $(whoami)."
echo "Please restart your terminal or run 'source ~/.zshrc' to apply changes."
else
echo "Antigen has been installed for user $(whoami), but the current shell is not Zsh."
echo "Please switch to Zsh or change your default shell to Zsh."
fi
Code language: PHP (php)
To create the script, run the following commands:
sudo nano /usr/local/bin/install_antigen
# and paste the script shown abovesudo chmod a+x /usr/local/bin/install_antigen
You can quickly install this script on your system with:
sudo 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_antigen
To change the default shell of your user you can use sudo nano /etc/passwd
and replace the default /bin/bash
with /bin/zsh
for them.
Initial server setup
The first thing we must do is update the server. For security reasons, it is crucial to keep your system as much to date as possible:
sudo apt-get update && sudo apt-get upgrade -y
More information about setting up the server:
Setup the locales
We will update the locales to en_US.UTF-8
:
sudo locale-gen en_US.UTF-8
echo "LANG=en_US.UTF-8" | sudo tee /etc/default/locale
echo "LANGUAGE=en_US.UTF-8" | sudo tee -a /etc/default/locale
echo "LC_ALL=en_US.UTF-8" | sudo tee -a /etc/default/locale
echo "LC_TYPE=en_US.UTF-8" | sudo tee -a /etc/default/locale
export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LC_TYPE=en_US.UTF-8
Commands that might be needed:
sudo apt install locales
sudo dpkg-reconfigure locales
More information:
Create a swap file
sudo fallocate -l 4G /swapfile || dd if=/dev/zero of=/swapfile bs=1024 count=$((4*1024*1024))
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab
Change the ssh port
We will assume that the new port is 2222 and we will also disable root login via ssh:
sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
Enable Fail2ban
Fail2ban is an intrusion prevention software framework that protects computer servers from brute-force attacks.
sudo apt-get install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
Change the default path for bash
I use sudo su <user>
quite a lot and by default, the path where I start is that of my own user. To change that to the user I am switching to, I like to change /etc/bash.bashrc
by adding cd ~
on the last line.
Install Virtualmin
To install Virtualmin, run the following commands:
wget https://software.virtualmin.com/gpl/scripts/virtualmin-install.sh
sudo sh virtualmin-install.sh --minimal --force
For security, the next step is to change the port for the Virtualmin interface. For this, you need to go to the Webmin
tab, Webmin > Webmin Configuration > Ports and Addresses
and change 10000
to whatever port you desire. Remember to add the new port to the firewall configuration, e.g. sudo ufw allow 9999/udp
The next thing I like to do is go to Virtualmin > System Settings > Virtualmin Configuration
and go though all the options. Some of the changes I do are:
- User interface settings: allow editing the limits when creating a server changed to yes;
- Defaults for new domains: domain name style in username changed to full domain name;
Since nowadays website are very space consuming, I usually update the default plan’s quota to 2GB.
Another thing I like to do is schedule a daily check for updates which will send me a notification via email and install security updates automatically.
More info:
- https://draghici.net/2020/05/02/virtualmin-hosting-on-debian-10-buster/
- https://www.virtualmin.com/download/
- https://www.virtualmin.com/documentation/installation/automated/
Enable the firewall
Apparently, Virtualmin now comes with Firewalld installed. It’s not bad and the web interface they provide is actually quite helpful. For a simpler command like solution, we can use ufw
. But for that, we must first stop firewalld, as managing the rules in both places might turn out challenging:
sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo apt purge firewalld
Uncomplicated Firewall (ufw
) is a program for managing a netfilter firewall designed to be easy to use, using a command-line interface.
sudo apt-get install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow in "WWW Full"
sudo ufw allow 2222/tcp
# use the port set for your ssh serversudo ufw allow 9999/udp
# remember to add the port for the virtualmin interfaceyes | sudo ufw enable
Remove usermin
In case you do not intend to let users use email on your server, you can simply uninstall usermin:
sudo usermin stop
sudo apt purge usermin
sudo rm -rf /etc/usermin
More info:
The first server
What I like to do next is create a server for the same host as the server’s itself. This was we will automatically have a Let's Encrypt
certificate for our hosting server. Once created, I update the contents of apache’s root directory with a nice game:
sudo su <your new username>
cd ~/public_html
rm index.html
git clone https://github.com/congerh/dino.git .
Enable custom php extensions
One thing we will probably have to do is to enable php extensions (e.g. curl, gd). To do that, we will have to know the version of php we are running (we will assume it’s 8.2.7
):
sudo apt-get install -y php8.2-curl
sudo systemctl restart apache2
A bash script to help with this is the following:
#!/bin/bash
# Script to enable specified PHP extensions
# Ensure the script is run as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit
fi
# Function to install and enable extensions for a given PHP version
enable_extensions() {
local php_version=$1
shift
local extensions=("$@")
echo "Installing extensions for PHP $php_version..."
for ext in "${extensions[@]}"; do
echo "Installing $ext..."
apt-get install -y "php${php_version}-${ext}"
done
echo "Restarting Apache to apply changes..."
systemctl restart apache2
echo "Extensions enabled for PHP $php_version."
}
# Get PHP version
php_version=$(php -v | grep '^PHP' | cut -d' ' -f2 | cut -d'.' -f1,2 | head -n 1)
if [[ -z "$php_version" ]]; then
echo "PHP is not installed or not found."
exit 1
fi
# Check if extensions are provided as command line arguments
if [ $# -eq 0 ]; then
echo "Enter PHP extensions to install (space-separated, e.g., 'curl gd mbstring'):"
read -ra extensions
else
extensions=("$@")
fi
enable_extensions "$php_version" "${extensions[@]}"
Code language: PHP (php)
To create the script, run the following commands:
sudo nano /usr/local/bin/enable_php_extension
# and paste the script shown abovesudo chmod a+x /usr/local/bin/enable_php_extension
Install composer
Some of the users might benefit from using composer with their website. For them, we will setup a script in /usr/local/bin
which they can run to install composer. The content of the script is:
#!/bin/bash
# This script installs Composer locally in the user's home directory
# Check if the script is run by a regular user, not root
if [ "$EUID" -eq 0 ]; then
echo "Please run as a regular user, not as root"
exit
fi
echo "Installing Composer for user: $(whoami)"
# Define the installation directory and the composer binary path
INSTALL_DIR=$HOME/.composer
COMPOSER_BIN=$HOME/composer
# Create the installation directory if it does not exist
mkdir -p $INSTALL_DIR
# Download Composer installer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
# Verify installer SHA-384
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")"
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
# Run the installer
php composer-setup.php --quiet --install-dir=$INSTALL_DIR --filename=composer
RESULT=$?
# Remove the installer
rm composer-setup.php
# Check if installation was successful
if [ $RESULT -eq 0 ]; then
echo "Composer installed successfully in $COMPOSER_BIN"
else
echo "Composer installation failed"
exit 1
fi
# Update PATH in .bashrc if the bin directory is not already in PATH
if [ -f $HOME/.bashrc ] && ! grep -q "$INSTALL_DIR" $HOME/.bashrc; then
echo "export PATH=\"\$PATH:$INSTALL_DIR\"" >> $HOME/.bashrc
echo "Please log out and log back in or source .bashrc to update PATH."
fi
# Update PATH in .zshrc if the bin directory is not already in PATH
if [ -f $HOME/.zshrc ] && ! grep -q "$INSTALL_DIR" $HOME/.zshrc; then
echo "export PATH=\"\$PATH:$INSTALL_DIR\"" >> $HOME/.zshrc
echo "Please log out and log back in or source .zshrc to update PATH."
fi
Code language: PHP (php)
To create the script, run the following commands:
sudo nano /usr/local/bin/install_composer
# and paste the script shown abovesudo chmod a+x /usr/local/bin/install_composer
The quick way to install this script is:
sudo wget -O /usr/local/bin/install_composer https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/install_composer.sh && sudo chmod +x /usr/local/bin/install_composer
Install nvm
We will create the following script which will make available installing nvm for the current user:
#!/bin/bash
# This script downloads and installs the latest version of NVM (Node Version Manager) for the current user
# Check if the script is run by a regular user, not root
if [ "$EUID" -eq 0 ]; then
echo "Please run as a regular user, not as root"
exit
fi
echo "Installing the latest version of NVM (Node Version Manager) for user: $(whoami)"
# Fetch the latest version tag from the NVM GitHub repository
LATEST_NVM_VERSION=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep 'tag_name' | cut -d\" -f4)
echo "Latest version of NVM: $LATEST_NVM_VERSION"
# Download and execute the install script for the latest version
curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/${LATEST_NVM_VERSION}/install.sh" | bash
# Add nvm to .bashrc
if [ -f $HOME/.bashrc ] && ! grep -q "NVM_DIR" $HOME/.bashrc; then
echo 'export NVM_DIR="$HOME/.nvm"' >> .bashrc
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> .bashrc
echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> .bashrc
echo "Please log out and log back in or source .bashrc to update PATH."
fi
# Add nvm to zshrc
if [ -f $HOME/.zshrc ] && ! grep -q "NVM_DIR" $HOME/.zshrc; then
echo 'export NVM_DIR="$HOME/.nvm"' >> .bashrc
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> .bashrc
echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> .bashrc
echo "Please log out and log back in or source .zshrc to update PATH."
fi
echo
echo "NVM installation complete."
Code language: PHP (php)
To create the script, run the following commands:
sudo nano /usr/local/bin/install_nvm
# and paste the script shown abovesudo chmod a+x /usr/local/bin/install_nvm
The quick way to install this script is:
sudo wget -O /usr/local/bin/install_nvm https://raw.githubusercontent.com/cristidraghici/debian-server-bash-scripts/master/install_nvm.sh && sudo chmod +x /usr/local/bin/install_nvm