Hosting your own websites on a *NIX server can be an empowering and cost-effective way to manage your web presence. Whether you are hosting a personal blog, an e-commerce site, or a portfolio, having control over your server can provide significant benefits in terms of performance, security, and customization. This guide will walk you through the process of setting up a fully functional hosting environment on a *NIX server using a simple Bash script, this is mainly designed for hosting woocommerce or wordpress sites with high or low traffic, assuming you have 8GB ram with 4 vcpu server.
Introduction
Running your own hosting server may seem daunting at first, but with the right guidance, it can be a straightforward process. This guide is designed to help you set up and manage your own hosting server using a Bash script. The script will handle the installation and configuration of essential services such as Nginx, MariaDB, and PHP, allowing you to focus on your websites rather than server administration.
Prerequisites
Before you begin, ensure that you have the following:
- A *NIX server: This guide is tailored for servers running Linux distributions like Ubuntu or Debian.
- Root or sudo access: You need administrative privileges to install and configure software.
- Basic knowledge of the command line: Familiarity with terminal commands will help you follow this guide.
Step-by-Step Guide
Step 1: Initial Setup
The first step is to prepare your server by updating the package lists and upgrading installed packages. This ensures that you have the latest security updates and software versions.
#!/bin/bash # Function to perform the initial setup initial_setup() { # Update and upgrade the system sudo apt update && sudo apt upgrade -y # Install necessary packages sudo apt install -y nginx mariadb-server mariadb-client software-properties-common apt-transport-https lsb-release ca-certificates curl # Add Sury PHP repository sudo curl -fsSL https://packages.sury.org/php/apt.gpg | sudo gpg --dearmor -o /usr/share/keyrings/php-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/php-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list # Update repository index sudo apt update # Install PHP versions sudo apt install -y php8.0 php8.0-fpm php8.0-mysql php8.0-cli php8.0-curl php8.0-gd php8.0-mbstring php8.0-xml php8.0-zip sudo apt install -y php7.4 php7.4-fpm php7.4-mysql php7.4-cli php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip # Start and enable services sudo systemctl start nginx sudo systemctl enable nginx sudo systemctl start mariadb sudo systemctl enable mariadb # Secure MariaDB installation sudo mysql_secure_installation # Adjust Nginx configuration sudo sed -i 's/# server_names_hash_bucket_size/server_names_hash_bucket_size 64;/' /etc/nginx/nginx.conf # Optimize Nginx for performance sudo cat <<EOL | sudo tee /etc/nginx/nginx.conf user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 1024; multi_accept on; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_tokens off; client_max_body_size 100M; # Gzip settings gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Cache settings open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; include /etc/nginx/mime.types; default_type application/octet-stream; # Logging Settings access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } EOL # Create SSL directory sudo mkdir -p /etc/nginx/ssl # Set timezone and enable NTP sudo timedatectl set-timezone America/New_York sudo timedatectl set-ntp on timedatectl # Optimize MariaDB for performance sudo cat <<EOL | sudo tee /etc/mysql/mariadb.conf.d/99-custom.cnf
[mysqld]
innodb_buffer_pool_size=4G innodb_log_file_size=1G innodb_flush_log_at_trx_commit=2 innodb_thread_concurrency=8 query_cache_size=64M query_cache_limit=2M max_connections=200 tmp_table_size=64M max_heap_table_size=64M EOL # Restart services to apply changes sudo systemctl restart nginx if [ $? -ne 0 ]; then echo “Failed to restart Nginx. Checking configuration…” sudo nginx -t echo “Please check the error logs and fix the issues before retrying.” exit 1 fi sudo systemctl restart mariadb echo “Initial setup and optimizations completed.” } initial_setup
Step 2: Adding a Domain
Adding a new domain involves creating a directory structure, generating SSL certificates, configuring Nginx, and setting up PHP-FPM. The script below simplifies this process:
# Function to add a domain add_domain() { DOMAIN=$1 SITE_TYPE=$2 PHP_VERSION=$3 # Check if PHP version is valid check_php_version $PHP_VERSION # Create directory for the domain sudo mkdir -p /home/$DOMAIN/htdocs sudo chown -R www-data:www-data /home/$DOMAIN/htdocs sudo chmod -R 755 /home/$DOMAIN # Generate self-signed SSL certificate if not exists SSL_DIR="/etc/nginx/ssl" SSL_CERT="$SSL_DIR/$DOMAIN.crt" SSL_KEY="$SSL_DIR/$DOMAIN.key" if [ ! -f "$SSL_CERT" ] || [ ! -f "$SSL_KEY" ]; then sudo mkdir -p $SSL_DIR sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $SSL_KEY -out $SSL_CERT -subj "/C=US/ST=State/L=City/O=Organization/OU=Department/CN=$DOMAIN" echo "Self-signed SSL certificate generated for $DOMAIN" fi # Create a new Nginx configuration file for the domain NGINX_CONF="/etc/nginx/sites-available/$DOMAIN" sudo cat <<EOL | sudo tee $NGINX_CONF server { listen 80; server_name $DOMAIN www.$DOMAIN; root /home/$DOMAIN/htdocs; index index.php index.html index.htm; location / { try_files \$uri \$uri/ /index.php?\$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php$PHP_VERSION-fpm-$DOMAIN.sock; } location ~ /\.ht { deny all; } listen 443 ssl; ssl_certificate $SSL_CERT; ssl_certificate_key $SSL_KEY; } EOL # Enable the new site sudo ln -s $NGINX_CONF /etc/nginx/sites-enabled/ # Create custom PHP configuration for the domain PHP_CONF_DIR="/etc/php/$PHP_VERSION/fpm/pool.d" PHP_CONF_FILE="$PHP_CONF_DIR/$DOMAIN.conf" # Determine the number of PHP-FPM workers based on site type if [ "$SITE_TYPE" == "heavy" ]; then PM_MAX_CHILDREN=10 PM_START_SERVERS=4 PM_MIN_SPARE_SERVERS=2 PM_MAX_SPARE_SERVERS=6 elif [ "$SITE_TYPE" == "light" ]; then PM_MAX_CHILDREN=5 PM_START_SERVERS=2 PM_MIN_SPARE_SERVERS=1 PM_MAX_SPARE_SERVERS=3 else echo "Invalid site type. Use 'heavy' or 'light'." exit 1 fi sudo cat <<EOL | sudo tee $PHP_CONF_FILE [$DOMAIN] user = www-data group = www-data listen = /var/run/php/php$PHP_VERSION-fpm-$DOMAIN.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = $PM_MAX_CHILDREN pm.start_servers = $PM_START_SERVERS pm.min_spare_servers = $PM_MIN_SPARE_SERVERS pm.max_spare_servers = $PM_MAX_SPARE_SERVERS chdir = / php_admin_value[memory_limit] = 512M php_admin_value[max_execution_time] = 60 php_admin_value[max_input_time] = 60 php_admin_value[max_input_vars] = 10000 php_admin_value[post_max_size] = 64M php_admin_value[upload_max_filesize] = 64M php_admin_value[date.timezone] = America/New_York php_admin_flag[display_errors] = off EOL # Restart PHP-FPM service to apply the new configuration sudo systemctl restart php$PHP_VERSION-fpm # Test Nginx configuration sudo nginx -t if [ $? -ne 0 ]; then echo "Nginx configuration test failed. Please check the configuration file." exit 1 fi # Create a new database for the domain DB_NAME=$(echo $DOMAIN | tr -d '.') DB_USER="${DB_NAME}_user" DB_PASS=$(openssl rand -base64 12) sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "CREATE DATABASE $DB_NAME;" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "FLUSH PRIVILEGES;" # Print database credentials echo "Database name: $DB_NAME" echo "Database user: $DB_USER" echo "Database password: $DB_PASS" # Restart Nginx to apply changes sudo systemctl restart nginx if [ $? -ne 0 ]; then echo "Failed to restart Nginx. Please check the status of the Nginx service." exit 1 fi echo "Domain $DOMAIN added successfully." }
Step 3: Removing a Domain
Removing a domain involves deleting the Nginx and PHP-FPM configurations, the domain’s directory, and the associated database. Here’s how you can automate this process:
# Function to remove a domain remove_domain() { if [ "$#" -ne 1 ]; then echo "Usage: $0 remove_domain domain_name" exit 1 fi DOMAIN=$1 # Remove Nginx configuration NGINX_CONF="/etc/nginx/sites-available/$DOMAIN" if [ -f $NGINX_CONF ]; then sudo rm $NGINX_CONF echo "Removed $NGINX_CONF" fi # Remove the symlink in sites-enabled NGINX_SYMLINK="/etc/nginx/sites-enabled/$DOMAIN" if [ -L $NGINX_SYMLINK ]; then sudo rm $NGINX_SYMLINK echo "Removed $NGINX_SYMLINK" fi # Remove PHP-FPM configuration PHP_CONF_DIR="/etc/php" for version in 7.4 8.0; do PHP_CONF_FILE="$PHP_CONF_DIR/$version/fpm/pool.d/$DOMAIN.conf" if [ -f $PHP_CONF_FILE ]; then sudo rm $PHP_CONF_FILE echo "Removed $PHP_CONF_FILE" fi done # Remove domain directory DOMAIN_DIR="/home/$DOMAIN" if [ -d $DOMAIN_DIR ]; then sudo rm -rf $DOMAIN_DIR echo "Removed $DOMAIN_DIR" fi # Remove the database and user DB_NAME=$(echo $DOMAIN | tr -d '.') DB_USER="${DB_NAME}_user" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "DROP DATABASE IF EXISTS $DB_NAME;" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "DROP USER IF EXISTS '$DB_USER'@'localhost';" sudo mysql --defaults-extra-file=$TEMP_MY_CNF -e "FLUSH PRIVILEGES;" echo "Removed database and user for $DOMAIN" # Restart Nginx to apply changes sudo systemctl restart nginx if [ $? -ne 0 ]; then echo "Failed to restart Nginx. Please check the status of the Nginx service." exit 1 fi echo "Domain $DOMAIN removed successfully." }
Step 4: Managing Databases
You can also automate the import and export of databases. This can be useful for backups or migrations.
Import Database
# Function to import a database import_database() { if [ "$#" -ne 2 ]; then echo "Usage: $0 import_database domain_name sql_gz_file_path" exit 1 fi DOMAIN=$1 SQL_GZ_FILE=$2 # Ensure the SQL file exists if [ ! -f "$SQL_GZ_FILE" ]; then echo "The file $SQL_GZ_FILE does not exist." exit 1 fi # Prompt for MySQL root password read -sp "Enter MySQL root password: " MYSQL_ROOT_PASSWORD echo # Create a temporary MySQL credentials file TEMP_MY_CNF=$(mktemp) chmod 600 $TEMP_MY_CNF cat <<EOF > $TEMP_MY_CNF
[client]
user=root password=$MYSQL_ROOT_PASSWORD EOF # Decompress the SQL file SQL_FILE=”${SQL_GZ_FILE%.gz}” gzip -dc “$SQL_GZ_FILE” > “$SQL_FILE” # Replace collation in the SQL file if needed sed -i ‘s/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g’ “$SQL_FILE” # Import the SQL file into the database DB_NAME=$(echo $DOMAIN | tr -d ‘.’) sudo mysql –defaults-extra-file=$TEMP_MY_CNF $DB_NAME < “$SQL_FILE” # Clean up by removing the decompressed SQL file rm “$SQL_FILE” rm $TEMP_MY_CNF echo “Database imported for $DOMAIN successfully.” # Set permissions for the WordPress directory setperm $DOMAIN }
Export Database
# Function to export a database export_database() { if [ "$#" -ne 2 ]; then echo "Usage: $0 export_database domain_name output_sql_gz_file_path" exit 1 fi DOMAIN=$1 OUTPUT_SQL_GZ_FILE=$2 # Prompt for MySQL root password read -sp "Enter MySQL root password: " MYSQL_ROOT_PASSWORD echo # Create a temporary MySQL credentials file TEMP_MY_CNF=$(mktemp) chmod 600 $TEMP_MY_CNF cat <<EOF > $TEMP_MY_CNF
[client]
user=root password=$MYSQL_ROOT_PASSWORD EOF # Export the database to a SQL file DB_NAME=$(echo $DOMAIN | tr -d ‘.’) SQL_FILE=”${OUTPUT_SQL_GZ_FILE%.gz}” sudo mysqldump –defaults-extra-file=$TEMP_MY_CNF $DB_NAME > “$SQL_FILE” # Compress the SQL file gzip “$SQL_FILE” # Clean up by removing the temporary MySQL credentials file rm $TEMP_MY_CNF echo “Database exported for $DOMAIN successfully to $OUTPUT_SQL_GZ_FILE.” }
Step 5: Setting Permissions
Setting the correct permissions for your WordPress directories is crucial for security and functionality.
# Function to set permissions for a WordPress directory setperm() { DOMAIN=$1 WP_PATH="/home/$DOMAIN/htdocs" # Ensure the directory exists if [ ! -d "$WP_PATH" ]; then echo "The directory $WP_PATH does not exist." exit 1 fi # Set ownership to www-data echo "Setting ownership to www-data for $WP_PATH..." sudo chown -R www-data:www-data $WP_PATH # Set permissions for directories and files in one command for better performance echo "Setting permissions for $WP_PATH..." sudo find $WP_PATH -type d -print0 | xargs -0 chmod 755 sudo find $WP_PATH -type f -print0 | xargs -0 chmod 644 # Set wp-config.php permissions to 600 if [ -f "$WP_PATH/wp-config.php" ]; then echo "Setting wp-config.php permissions to 600..." sudo chmod 600 $WP_PATH/wp-config.php else echo "wp-config.php not found in $WP_PATH. Skipping..." fi echo "Permissions have been set for $WP_PATH." }
Main Script
Here’s the main script that ties all the functions together:
# Main script if [ "$1" == "setup" ]; then initial_setup elif [ "$1" == "add_domain" ]; then shift add_domain "$@" elif [ "$1" == "remove_domain" ]; then shift remove_domain "$@" elif [ "$1" == "auto_add_domains" ]; then auto_add_domains elif [ "$1" == "import_database" ]; then shift import_database "$@" elif [ "$1" == "export_database" ]; then shift export_database "$@" elif [ "$1" == "setperm" ]; then shift setperm "$@" else echo "Usage: $0 {setup|add_domain|remove_domain|auto_add_domains|import_database|export_database|setperm} [arguments]" exit 1 fi
Let’s break down the usage of the script and explain each command and its arguments:
echo "Usage: $0 {setup|add_domain|remove_domain|auto_add_domains|import_database|export_database|setperm} [arguments]"
Script Usage
The script supports several commands to manage your hosting environment. Here’s a brief explanation of each command and how to use it:
1. setup
Usage: ./script.sh setup
- Purpose: Perform the initial setup of your server. This includes updating the system, installing necessary packages (Nginx, MariaDB, PHP), securing MariaDB, and configuring Nginx and MariaDB for optimal performance.
- Arguments: None.
2. add_domain
Usage: ./script.sh add_domain <domain_name> <site_type> <php_version>
- Purpose: Add a new domain to your server. This involves creating a directory structure, generating a self-signed SSL certificate, configuring Nginx, setting up PHP-FPM, and creating a new database.
- Arguments:
<domain_name>
: The domain name you want to add (e.g.,example.com
).<site_type>
: The type of site, which determines the PHP-FPM worker configuration. Useheavy
for high-traffic sites andlight
for low-traffic sites.<php_version>
: The PHP version to use. Supported versions are7.4
and8.0
.
3. remove_domain
Usage: ./script.sh remove_domain <domain_name>
- Purpose: Remove an existing domain from your server. This includes deleting the Nginx and PHP-FPM configurations, the domain’s directory, and the associated database.
- Arguments:
<domain_name>
: The domain name you want to remove (e.g.,example.com
).
4. auto_add_domains
Usage: ./script.sh auto_add_domains
- Purpose: Automatically add a predefined list of domains to your server. This is useful for quickly setting up multiple domains with specific configurations.
- Arguments: None.
5. import_database
Usage: ./script.sh import_database <domain_name> <sql_gz_file_path>
- Purpose: Import a database for a specified domain from a compressed SQL file. This is useful for restoring backups or migrating databases.
- Arguments:
<domain_name>
: The domain name associated with the database (e.g.,example.com
).<sql_gz_file_path>
: The path to the compressed SQL file (e.g.,/path/to/database.sql.gz
).
6. export_database
Usage: ./script.sh export_database <domain_name> <output_sql_gz_file_path>
- Purpose: Export the database of a specified domain to a compressed SQL file. This is useful for creating backups or migrating databases.
- Arguments:
<domain_name>
: The domain name associated with the database (e.g.,example.com
).<output_sql_gz_file_path>
: The path to save the compressed SQL file (e.g.,/path/to/backup.sql.gz
).
7. setperm
Usage: ./script.sh setperm <domain_name>
- Purpose: Set the correct permissions for a WordPress directory associated with a specified domain. This ensures that the web server can read and write files as needed while maintaining security.
- Arguments:
<domain_name>
: The domain name whose directory permissions you want to set (e.g.,example.com
).
Example Commands
Here are a few examples of how to use the script:
- Initial Setup:
./script.sh setup
- Add a Domain:
./script.sh add_domain example.com light 7.4
- Remove a Domain:
./script.sh remove_domain example.com
- Automatically Add Predefined Domains:
./script.sh auto_add_domains
- Import a Database:
./script.sh import_database example.com /path/to/database.sql.gz
- Export a Database:
./script.sh export_database example.com /path/to/backup.sql.gz
- Set Permissions for a Domain:
./script.sh setperm example.com
With these commands, you can efficiently manage your hosting environment on any *NIX server. This script simplifies the process of setting up, configuring, and maintaining your web hosting services.
Conclusion
Setting up your own hosting server can be a rewarding experience. With this comprehensive guide and the provided Bash script, you can easily manage your server, add or remove domains, and handle database operations with ease. The power of automation allows you to focus more on developing your websites and less on the intricacies of server management. Happy hosting!