Become root #
sudo -i
Create a normal deploy user #
adduser deploy
usermod -aG www-data deploy
Optional but recommended for small VMs: 2G swap #
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo ‘/swapfile none swap sw 0 0’ >> /etc/fstab
Install packages #
apt update && apt -y upgrade
apt -y install nginx mariadb-server \
php8.3-fpm php8.3-cli php8.3-mysql php8.3-xml php8.3-zip \
php8.3-mbstring php8.3-intl php8.3-gd php8.3-curl \
git unzip curl composer \
certbot python3-certbot-nginx
systemctl enable –now nginx php8.3-fpm mariadb
2) MariaDB – DB & user for Drupal #
mysql -u root <<‘SQL’
CREATE DATABASE portal_drupal DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER ‘portal_user’@’localhost’ IDENTIFIED BY ‘StrongPassword!’;
GRANT ALL PRIVILEGES ON portal_drupal.* TO ‘portal_user’@’localhost’;
FLUSH PRIVILEGES;
SQL
Folders & permissions #
mkdir -p /var/www/joatco-op.com/public
mkdir -p /var/www/portal.joatco-op.com
chown -R deploy:www-data /var/www/joatco-op.com /var/www/portal.joatco-op.com
find /var/www -type d -exec chmod 775 {} \;
find /var/www -type f -exec chmod 664 {} \;
Simple apex landing page #
cat > /var/www/joatco-op.com/public/index.html <<‘EOF’
JOATCO-OP
Portal is at portal.joatco-op.com.
EOF
Nginx server blocks #
Apex: static #
cat > /etc/nginx/sites-available/joatco-op.com <<‘EOF’
server {
listen 80;
server_name joatco-op.com www.joatco-op.com;
root /var/www/joatco-op.com/public;
index index.html;
location / { try_files $uri $uri/ =404; }
}
EOF
Portal: Drupal on /web with PHP-FPM 8.3 #
cat > /etc/nginx/sites-available/portal.joatco-op.com <<‘EOF’
server {
listen 80;
server_name portal.joatco-op.com;
root /var/www/portal.joatco-op.com/web;
index index.php index.html;
# Drupal front controller
location / {
try_files $uri /index.php?$query_string;
}
# PHP
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# Deny access to sensitive files
location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$ { deny all; }
location ~* \.(yml|yaml|twig|md|txt)$ { deny all; }
}
EOF
ln -s /etc/nginx/sites-available/joatco-op.com /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/portal.joatco-op.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Composer-based Drupal install (as deploy
) #
sudo -u deploy -H bash -lc ‘
set -e
cd /var/www/portal.joatco-op.com
export COMPOSER_MEMORY_LIMIT=-1
composer create-project drupal/recommended-project .
composer require drush/drush
mkdir -p web/sites/default/files
cp web/sites/default/default.settings.php web/sites/default/settings.php
echo “$settings[\”trusted_host_patterns\”] = [\”^portal.joatco-op.com$\”];”
‘ | sudo tee -a /var/www/portal.joatco-op.com/web/sites/default/settings.php >/dev/null
If there are issue with above, run this: #
—- FIX USER + BUILD DRUPAL (minimal, idempotent) —- #
set -e
1) Create deploy user if missing; add to www-data #
id -u deploy >/dev/null 2>&1 || adduser –disabled-password –gecos “” deploy
usermod -aG www-data deploy
2) Ensure portal dir and ownership #
PORTAL_DOMAIN=”portal.joatco-op.com”
PORTAL_ROOT=”/var/www/$PORTAL_DOMAIN”
mkdir -p “$PORTAL_ROOT”
chown -R deploy:www-data “$PORTAL_ROOT”
3) Make sure PHP CLI + Composer exist #
apt update
apt -y install php8.3-cli php8.3-zip php8.3-xml php8.3-mbstring php8.3-intl php8.3-gd unzip git curl composer
COMPOSER_BIN=”$(command -v composer)”
4) Build Drupal (Composer layout -> /web) as deploy #
sudo -u deploy -H bash -lc “
set -e
cd ‘$PORTAL_ROOT’
export COMPOSER_MEMORY_LIMIT=-1
if [ ! -f web/index.php ]; then
$COMPOSER_BIN create-project drupal/recommended-project .
$COMPOSER_BIN require drush/drush
mkdir -p web/sites/default/files
cp web/sites/default/default.settings.php web/sites/default/settings.php
else
echo ‘Drupal already present, skipping create.’
fi
“
5) Permissions + trusted host #
chown -R deploy:www-data “$PORTAL_ROOT”
find “$PORTAL_ROOT” -type d -exec chmod 775 {} \;
find “$PORTAL_ROOT” -type f -exec chmod 664 {} \;
echo “\$settings[‘trusted_host_patterns’] = [‘^portal.joatco-op.com$’];” \
| tee -a “$PORTAL_ROOT/web/sites/default/settings.php” >/dev/null
6) Reload Nginx and test vhost routing (Host header) #
nginx -t && systemctl reload nginx
curl -I -H “Host: $PORTAL_DOMAIN” http://127.0.0.1/
Make sure thing are working: #
grep -R “server_name portal.joatco-op.com” -n /etc/nginx/sites-enabled/* -n
grep -R “root /var/www/portal.joatco-op.com/web” -n /etc/nginx/sites-enabled/* || echo “root not set to /web”
nginx -t && systemctl reload nginx
Verify the file is actually there:
ls -l /var/www/portal.joatco-op.com/web/index.php
make sure firewall is open. #
Once you get 200 OK
, open https://portal.joatco-op.com
and finish the Drupal installer using:
- DB:
portal_drupal
- User:
portal_user
- Pass: the one you set
Install Cert with Cert Bot.