{
    "title": "My Debian 12 (bookworm) server set-up",
    "slug": "my-debian-12-bookworm-server-set-up",
    "excerpt": "I've been running Debian on my servers for years. It's dependable. I guess my server set-up is pretty common, consisting of Apache, PHP and MariaDB, but I figure it is still worth sharing details of how I provision my servers.",
    "body": "I've been running Debian on my servers for years. It's dependable. I guess my server set-up is pretty common, consisting of Apache, PHP and MariaDB, but I figure it is still worth sharing. That said, some of the commands and configuration settings below are purposely generic, if you follow any of this, you should probably check the commands and their output before running them.\n\nThis post covers Debian 12 (bookworm) only.\n\n## Installation\n\nI install Debian using the [netinst image](https://www.debian.org/distrib/) and I deselect all options in [tasksel](https://wiki.debian.org/tasksel), apart from the SSH Server and Standard System Utilities. This provides for a very minimal installation and good starting point to set-up my server with packages of my own choosing.\n\n![Screenshot of tasksel showing SSH server and Standard System Utilities selections](https://blog.philipnewborough.co.uk/media/67d7cb74-0888-4293-a436-f8351c350c83.png)\n\nNote, detailing how to install Debian is beyond the scope of this post, but if you need some guidance, please see the [Debian Installation Guide](https://www.debian.org/releases/stable/installmanual).\n\n## Post Installation\n\nIt probably does not need to be mentioned, but post installation steps can be run on any Debian install. For example, I normally perform these steps on my desktop install of Debian to provide a working development environment.\n\n### Install packages\n\nOnce the installation is complete, I install the following packages. Most of the packages relate to Apache, PHP and MariaDB, but there are some utility packages included, as well as fish, my preferred shell.\n\n```\nsudo apt install git curl fish wget unzip bat apache2 apache2-bin apache2-data apache2-utils mariadb-client mariadb-server php php-fpm php8.2 php-common php-gd php-getid3 php-mysql php8.2-fpm php8.2-cli php8.2-common php8.2-gd php8.2-mysql php-ldap php8.2-redis php8.2-opcache php8.2-soap php8.2-readline php8.2-curl php8.2-xml php-imagick php8.2-intl php8.2-zip php8.2-mbstring ssl-cert imagemagick php-imagick redis-server php-curl ntpsec php-bcmath php-gmp php-mbstring php-curl php-mbstring php-sqlite3 sqlite3\n```\n\n### Configure Apache\n\nI issue the following commands to configure Apache and enable Apache modules:\n\n```\nsudo a2enmod proxy_fcgi setenvif && sudo a2enconf php8.2-fpm && sudo systemctl reload apache2 && sudo a2enmod rewrite && sudo a2enmod ssl && sudo a2enmod http2 && sudo a2enmod headers && sudo a2enmod rewrite && sudo a2enmod ssl && sudo systemctl restart apache2\n```\n\nApache virtual host files can be found in the `/etc/apache2/sites-available` directory and have a `.conf` extension. Enabled virtual host files are linked to these files and can found in the `/etc/apache2/sites-enabled` directory. To enable a virtual host file, enter the following command, where `virtualhostfile` corresponds to the virtual host file located in `/etc/apache2/sites-available` without the `.conf` file extension.\n\n```\nsudo a2ensite virtualhostfile\n```\n\nYou can disable virtual hosts with the following command:\n\n```\nsudo a2dissite virtualhostfile\n```\n\nYou will need to reload Apache after any changes are made. This can be achieved with the following command:\n\n```\nsudo systemctl reload apache2\n```\n\n#### Example Apache Virtual Host file\n\nI thought it might be handy to include an example virtual host file. The example below is from my own development environment, you will need to change the `DocumentRoot` and `Directory` path names to suit. The example is a virtual host file for use with CodeIgniter 4 and once it is set-up, the host should be reachable on the domain `http://codeigniter.localhost`. \n\n```\n<VirtualHost *:80>\n    DocumentRoot /home/username/Projects/codeigniter/public\n    ServerName codeigniter.localhost\n    <Directory \"/home/username/Projects/codeigniter/public\">\n        AllowOverride All\n        Options MultiViews Indexes FollowSymLinks\n        Require all granted\n    </Directory>\n    RewriteEngine on\n</VirtualHost>\n```\n\n### Configure PHP\n\nI run the following commands to modify some PHP settings such file upload and execution time limits. You should adjust these to suit your needs:\n\n```\nsudo sed -i 's/memory_limit = 128M/memory_limit = 512M/g' /etc/php/8.2/fpm/php.ini && sudo sed -i 's/post_max_size = 8M/post_max_size = 128M/g' /etc/php/8.2/fpm/php.ini && sudo sed -i 's/max_file_uploads = 20/max_file_uploads = 30/g' /etc/php/8.2/fpm/php.ini && sudo sed -i 's/max_execution_time = 30/max_execution_time = 900/g' /etc/php/8.2/fpm/php.ini && sudo sed -i 's/max_input_time = 60/max_input_time = 3000/g' /etc/php/8.2/fpm/php.ini && sudo sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 128M/g' /etc/php/8.2/fpm/php.ini && sudo systemctl restart php8.2-fpm\n```\n\n#### Install composer\n\nI use [composer](https://getcomposer.org) to install and manage PHP applications and libraries. You can install composer with the following commands. Note, there is no hash check in these commands, use at your own risk. See official documentation for [full installation instructions](https://getcomposer.org/download/).\n\n```\nphp -r \"copy('https://getcomposer.org/installer', 'composer-setup.php');\"\nphp composer-setup.php\nphp -r \"unlink('composer-setup.php');\"\nsudo mv composer.phar /usr/local/bin/composer\n```\n\n### Configure MariaDB\n\nI have found that MariaDB works pretty well out-of-the-box on Debian and is configured with sane default settings. That said, you may or may not want to edit some settings depending on how database dependant your applications are. The main configuration file is located at `/etc/mysql/mariadb.conf.d/50-server.cnf`. Some additional settings that are not included in that file and that you might want to take a look at include:\n\n```\n# InnoDB Settings\ninnodb_buffer_pool_size = 3G  # Allocates 3GB of memory for the InnoDB buffer pool to cache data and indexes, reducing disk I/O.\ninnodb_log_file_size = 512M  # Sets the size of each InnoDB log file, improving write performance and reducing log rotations.\ninnodb_flush_method = O_DIRECT  # Uses O_DIRECT to bypass filesystem cache, minimizing double buffering and improving performance.\ninnodb_io_capacity = 2000  # Sets the I/O capacity for InnoDB background tasks, suitable for fast storage like SSDs.\n\n# Connection Settings\nmax_connections = 500  # Limits the maximum number of concurrent database connections.\nthread_cache_size = 50  # Specifies the number of threads cached for reuse, reducing thread creation overhead.\nthread_handling = pool-of-threads  # Enables thread pooling, which is more efficient for high-concurrency workloads.\n\n# Cache and Buffer Settings\ntmp_table_size = 64M  # Maximum size for in-memory temporary tables; larger tables will be written to disk.\nmax_heap_table_size = 64M  # Maximum size for user-created in-memory tables, working with tmp_table_size.\ntable_open_cache = 2000  # Number of open tables the server can cache to improve performance.\ntable_definition_cache = 2000  # Number of table definitions cached to reduce overhead when opening tables.\njoin_buffer_size = 4M  # Allocates memory for joins without indexes; larger size improves performance for complex joins.\nsort_buffer_size = 4M  # Memory allocated for sorting operations; larger size enhances ORDER BY and GROUP BY performance.\n\n# Logging Settings\nslow_query_log = 1  # Enables logging of slow queries to help identify and optimize inefficient queries.\nslow_query_log_file = /var/log/mysql/mariadb-slow.log  # Specifies the location of the slow query log file.\nlong_query_time = 1  # Logs queries that take longer than 1 second to execute.\nlog_queries_not_using_indexes = 1  # Logs queries that don’t use indexes, helping identify areas for optimization.\n\n# Disable Query Cache\nquery_cache_type = 0  # Disables the query cache, which is often ineffective for workloads with frequent data changes.\nquery_cache_size = 0  # Sets the query cache size to 0, ensuring it is fully disabled and does not consume memory.\n```\n\nMariaDB will need restarting after any changes to the file, this can be achieved with the following command:\n\n```\nsudo systemctl restart mariadb\n```\n\n#### Create MariaDB user\n\nTo create a new admin user for MariaDB, start the MariaDB client with the following command:\n\n```\nsudo mariadb\n```\n\nOnce opened, enter the following statements to create a new user with all privileges on localhost. Note, change the username and password to suit.\n\n```\nCREATE USER 'username'@'localhost' IDENTIFIED BY 'password';\nGRANT ALL PRIVILEGES ON *.* TO 'username'@'localhost' WITH GRANT OPTION;\n```\n\nOnce the statements have been run, type `exit` to quit the client.\n\n#### Create MariaDB user configuration file\n\nIf you are going to be running any automated DB tasks such as backups, it might be handy to create a personal MariaDB configuration file to store your MariaBD username and password. This will allow you to write scripts that perform DB dumps without having to include your password in the script. The configuration file can be edited with the following command:\n\n```\nnano ~/.my.cnf\n```\n\nInclude the following content in the file, changing the username and password to suit:\n\n```\n[mysql]\nuser = myusername\npassword = mypassword\n\n[mysqldump]\nuser = myusername\npassword = mypassword\n```\n\nOnce the file has been created, you should modify the permissions so only you can read/write. This is done with the following command:\n\n```\nchmod 600 ~/.my.cnf\n```\n\n### Install Node.js\n\nIf required, I install Node.js via the [NodeSource](https://nodesource.com) Debian repository using the following shell script. At the time of writing I am using Node.js version 22. You can change which version of Node.js is installed by editing the `NODE_MAJOR` variable.\n\n```\n#!/usr/bin/bash\nsudo apt-get update\nsudo apt-get install -y ca-certificates curl gnupg\nsudo mkdir -p /etc/apt/keyrings\ncurl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg\n\nNODE_MAJOR=22\necho \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main\" | sudo tee /etc/apt/sources.list.d/nodesource.list\n\nsudo apt-get update\nsudo apt-get install nodejs -y\n\nexit\n```\n\n### Configure UFW Firewall\n\nIf the server is public facing, I set-up a firewall using UFW. UFW can be installed with the following command:\n\n```\nsudo apt install ufw\n```\n\nOnce installed, the following commands will configure the firewall to allow web traffic and SSH connections:\n\n```\nsudo ufw allow OpenSSH\nsudo ufw allow WWW\nsudo ufw allow \"WWW Secure\"\nsudo ufw enable\n```\n\nYou can check the status of the firewall with the following command:\n\n```\nsudo ufw status verbose\n```\n\nIf configured correctly, the output of the above command should look similar to below:\n\n```\nStatus: active\nLogging: on (low)\nDefault: deny (incoming), allow (outgoing), disabled (routed)\nNew profiles: skip\n\nTo                         Action      From\n--                         ------      ----\n22/tcp (OpenSSH)           ALLOW IN    Anywhere                  \n80/tcp (WWW)               ALLOW IN    Anywhere                  \n443/tcp (WWW Secure)       ALLOW IN    Anywhere                  \n22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)             \n80/tcp (WWW (v6))          ALLOW IN    Anywhere (v6)             \n443/tcp (WWW Secure (v6))  ALLOW IN    Anywhere (v6)\n```\n\n### Set-up fish shell\n\nThis is a personal preference, but I use [fish](https://fishshell.com) as my default shell, preferring it over the default Bash shell. I set fish as the default shell with the following command:\n\n```\nchsh -s /usr/bin/fish\n```\n\nNote, the above command will prompt you for your user password. Also, you will need to logout before it will become the default shell. Meanwhile, you can use fish by typing `fish` at the Bash prompt.\n\n#### Oh My Fish and Pure theme\n\nI use the [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish ) framework to enhance my fish experience. It can be installed with the following command:\n\n```\ncurl https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install | fish\n```\n\nOnce installed, I install the Pure theme with the following command:\n\n```\nomf install pure\n```\n\n## Finishing touches\n\nFinishing touches might include things such as importing or creating SSH keys etc. Other than that, any other changes would be specific to the applications being hosted or developed on the server.",
    "tags": [],
    "published_at": "2024-11-20 19:29:12",
    "url": "https://blog.philipnewborough.co.uk/posts/my-debian-12-bookworm-server-set-up",
    "featured_image": "https://blog.philipnewborough.co.uk/media/og-b3348788-bf1e-4998-9632-63954d6961cd.png"
}