2
\$\begingroup\$

The following bash script is intended to be run on a freshly installed digitaloceans ubuntu 20.04 droplet by root in its home dir.

The purpose is to setup a ready to use webserver with SSL (certbot), PHP 7.4 support (fastcgi), an Apache2 fileserver and nginx as reverse proxy. DNS is managed by Cloudflare.

My main questions are if the settings for apache2, nginx and fastcgi are justifiable and if there are recommendations to improve the script.

#!/bin/bash
set -e # Terminate if script has error


####################################################################
# Variables used
####################################################################

sitedir=""
fqn=""
sdub=""
myip=""
cftoken=""
cfmail=""
webmail=""
servername=""
vhostserver=""
myip="$(curl ipecho.net/plain; echo)"


####################################################################
# Query for location
####################################################################
printf "
####################################################################
# Enter location on disk for site (Default: /var/www/\$fqn):
####################################################################\n
dir: "
read -rp "" sitedir


####################################################################
# Query for FQN
####################################################################
printf "
####################################################################
# Enter FQN for website:
####################################################################\n
fqn: "
read -rp "" fqn


####################################################################
# Query for subdomain
####################################################################
printf "
####################################################################
# Enter subdomain for FQN:
####################################################################\n
sub: "
read -rp "" sub


####################################################################
# Query for webmaster mail address
####################################################################
printf "
####################################################################
# Enter webmaster mail address for site: (Default: webmaster@\$fqn)
####################################################################\n
webmaster: "
read -rp "" webmail


####################################################################
# Query non-root user credentials
####################################################################
printf "
####################################################################
# Enter CLOUDFLARE-TOKEN:
####################################################################\n
cftoken: "
read -rp "" cftoken


####################################################################
# Query non-root user credentials
####################################################################
printf "
####################################################################
# Enter CLOUDFLARE-mail:
####################################################################\n
cfmail: "
read -rp "" cfmail


####################################################################
# setting defaults
####################################################################

# sitedir
if [ -n "$sitedir" ]; then
    if [ ${sitedir:+1} ]
    then
        echo "yes"
    fi
else
    sitedir="/var/www/$fqn"
fi

# webmaster mail
if [ -n "$webmail" ]; then
    if [ ${webmail:+1} ]
    then
        echo "yes"
    fi
else
    webmail=webmaster@$fqn
fi

# servername
if [ -n "$sub" ]; then
    servername="server_name $sub.$fqn www.$sub.$fqn;"
    vhostserver="ServerName $sub.$fqn
    ServerAlias www.$sub.$fqn"
else
    servername="server_name $fqn www.$fqn;"
    vhostserver="ServerName $fqn
    ServerAlias www.$fqn"
fi


####################################################################
# Enable basic firewall rules
####################################################################

ufw allow OpenSSH
ufw enable


####################################################################
# Install required software
####################################################################

apt update
apt -y upgrade

# EXA
wget -c http://old-releases.ubuntu.com/ubuntu/pool/universe/r/rust-exa/exa_0.9.0-4_amd64.deb
apt-get install ./exa_0.9.0-4_amd64.deb

# basics
apt -y install tree gnupg2 git net-tools original-awk fzf silversearcher-ag unzip pkg-config \
    openssl apache2 php-fpm php-xml libtool

# fastcgi for apache2
wget https://mirrors.edge.kernel.org/ubuntu/pool/multiverse/liba/libapache-mod-fastcgi/libapache2-mod-fastcgi_2.4.7~0910052141-1.2_amd64.deb
dpkg -i libapache2-mod-fastcgi_2.4.7~0910052141-1.2_amd64.deb


####################################################################
# setup of apache2
####################################################################

# set apache listening on loopback for ipv4 only
mv /etc/apache2/ports.conf /etc/apache2/ports.conf.default
echo "Listen 0.0.0.0:8080" | tee /etc/apache2/ports.conf

# make working dir for server
mkdir -p $sitedir

# disable factory defaults
a2dissite 000-default

# configure apache for use  of mod_fastcgi
a2enmod actions
a2enmod proxy_fcgi setenvif
systemctl restart apache2

a2enconf php7.4-fpm
systemctl reload apache2

# backup default fastcgi.conf
mv /etc/apache2/mods-enabled/fastcgi.conf /etc/apache2/mods-enabled/fastcgi.conf.default

# setting new defaults for fastcgi
cat << EOF > /etc/apache2/mods-enabled/fastcgi.conf
<IfModule mod_fastcgi.c>
  AddHandler fastcgi-script .fcgi
  FastCgiIpcDir /var/lib/apache2/fastcgi
  AddType application/x-httpd-fastphp .php
  Action application/x-httpd-fastphp /php-fcgi
  Alias /php-fcgi /usr/lib/cgi-bin/php-fcgi
  FastCgiExternalServer /usr/lib/cgi-bin/php-fcgi -socket /run/php/php7.4-fpm.sock -pass-header Authorization
  <Directory /usr/lib/cgi-bin>
    Require all granted
  </Directory>
</IfModule>
EOF
systemctl restart apache2

# create a php-info page - to be removed before production
echo "<?php phpinfo(); ?>" | tee /var/www/$fqn/info.php

# adjust ufw
ufw allow 8080
ufw allow "Apache Full"

# mod_rpaf to get visitors real IP through cloudflare and reverse proxy
apt -y install build-essential apache2-dev libtool-bin
wget https://github.com/gnif/mod_rpaf/archive/stable.zip
unzip stable.zip && cd mod_rpaf-stable
make && make install && libtool --finish /usr/lib/apache2/modules

touch /etc/apache2/mods-available/rpaf.load
echo 'LoadModule rpaf_module /usr/lib/apache2/modules/mod_rpaf.so' >> /etc/apache2/mods-available/rpaf.load

a2enmod rpaf
systemctl restart apache2

# Set defaults for virtual host
cat << EOF > /etc/apache2/sites-available/$fqn.conf
<VirtualHost *:8080>
    $vhostserver
    ServerAdmin $webmail
    DocumentRoot $sitedir
    <IfModule mod_ssl.c>
        SSLEngine On
        SSLHonorCipherOrder On
        SSLCompression Off
        SSLSessionTickets Off
        SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
        SSLCipherSuite EECDH+CHACHA20:EECDH+AES
    </IfModule>
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTPS} =on
        RewriteRule ^ - [E=PROTO:https]
        RewriteCond %{HTTPS} !=on
        RewriteRule ^ - [E=PROTO:http]
        # RewriteCond %{HTTPS} !=on
        RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
        RewriteRule ^ %{ENV:PROTO}://%1%{REQUEST_URI} [R=301,L]
    </IfModule>
    <IfModule mod_headers.c>
        Header always set Referrer-Policy "strict-origin-when-cross-origin" "expr=%{CONTENT_TYPE} =~ m#text\/(css|html|javascript)|application\/pdf|xml#i"
    </IfModule>
    <IfModule mod_headers.c>
        Header always set X-Content-Type-Options "nosniff"
    </IfModule>
    <IfModule mod_headers.c>
        Header always set X-Frame-Options "DENY" "expr=%{CONTENT_TYPE} =~ m#text/html#i"
    </IfModule>
    <IfModule mod_setenvif.c>
      <IfModule mod_headers.c>
          <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">
              SetEnvIf Origin ":" IS_CORS
              Header set Access-Control-Allow-Origin "*" env=IS_CORS
          </FilesMatch>
      </IfModule>
    </IfModule>
    <IfModule mod_headers.c>
      <FilesMatch "\.(eot|otf|tt[cf]|woff2?)$">
          Header set Access-Control-Allow-Origin "*"
      </FilesMatch>
    </IfModule>
    <IfModule rpaf_module>
      RPAF_Enable On
      RPAF_SetHostName On
      RPAF_ProxyIPs $myip
      RPAF_Header CF-CONNECTING-IP
    </IfModule>
  
    # Log format config
    LogFormat "%{CF-CONNECTING-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forwarded
    CustomLog "/var/log/apache2/$fqn.access.log" forwarded
    ErrorLog /var/log/apache2/$fqn.error.log
    <Directory $sitedir>
        AllowOverride All
    </Directory>
</VirtualHost>
EOF

a2ensite $fqn.conf
systemctl restart apache2

# get SSL certificates by certbot
apt-get -y install python3-certbot-dns-cloudflare

# setup CF credentials
mkdir /root/.secrets/ && touch /root/.secrets/cloudflare.ini
echo "dns_cloudflare_email=$cfmail" >> /root/.secrets/cloudflare.ini
echo "dns_cloudflare_api_key=$cftoken" >> /root/.secrets/cloudflare.ini
chmod 0700 /root/.secrets/
chmod 0400 /root/.secrets/cloudflare.ini

# go
certbot certonly --agree-tos --no-eff-email --email \
$webmail --dns-cloudflare --dns-cloudflare-credentials \
/root/.secrets/cloudflare.ini -d $fqn,*.$fqn --preferred-challenges dns-01


####################################################################
# nginx setup
####################################################################

apt -y install nginx

# remove default
rm /etc/nginx/sites-enabled/default


# setup new defaults
cat << EOF > /etc/nginx/sites-available/apache
server {
  listen 80;
  $servername
  location / {
     return 301 https://\$host\$request_uri;
  }
}
server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  
  $servername
  ssl_session_timeout 24h;
  ssl_session_cache shared:SSL:10m;
  keepalive_timeout 300s;
  # ssl_buffer_size 1400;
  ssl_session_tickets off;
  ssl_prefer_server_ciphers on;
  ssl_certificate /etc/letsencrypt/live/$fqn/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/$fqn/privkey.pem;
  ssl_dhparam /usr/lib/python3/dist-packages/certbot/ssl-dhparams.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers EECDH+CHACHA20:EECDH+AES;
  ssl_ecdh_curve X25519:prime256v1:secp521r1:secp384r1;
  return 301 $scheme://www.$fqn$request_uri;
}
server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  
  server_name www.$fqn
  ssl_session_timeout 24h;
  ssl_session_cache shared:SSL:10m;
  keepalive_timeout 300s;
  # ssl_buffer_size 1400;
  ssl_session_tickets off;
  ssl_prefer_server_ciphers on;
  ssl_certificate /etc/letsencrypt/live/$fqn/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/$fqn/privkey.pem;
  ssl_dhparam /usr/lib/python3/dist-packages/certbot/ssl-dhparams.pem;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers EECDH+CHACHA20:EECDH+AES;
  ssl_ecdh_curve X25519:prime256v1:secp521r1:secp384r1;
  location / {
      proxy_pass http://$myip:8080;
      proxy_set_header Host \$host;
      proxy_set_header X-Real-IP \$remote_addr;
      proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto \$scheme;
      proxy_read_timeout 600;
      proxy_buffer_size 128k;
      proxy_buffers   4 256k;
      proxy_busy_buffers_size   256k;
      client_max_body_size 100M;
  }
  location ~ /\.ht {
      deny all;
  }
#  add_header Referrer-Policy $referrer_policy always;
  add_header X-Content-Type-Options nosniff always;
#  add_header X-Frame-Options $x_frame_options always;
  location ~* /\.(?!well-known\/) {
  deny all;
}
  location ~* (?:#.*#|\.(?:bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$ {
  deny all;
}
#  add_header Access-Control-Allow-Origin $cors;
  access_log /var/log/nginx/$fqn.access.log;
  error_log /var/log/nginx/$fqn.error.log;
}
EOF

# activate new settings
ln -s /etc/nginx/sites-available/apache /etc/nginx/sites-enabled/apache
systemctl restart nginx
\$\endgroup\$
5
  • \$\begingroup\$ Hi Roadrunner85. This site is for code reviews, which isn't what you sound like you are looking for. Asking about your web server settings probably belongs on another Stack Exchange site like security, webmasters, or Unix&Linux. \$\endgroup\$
    – chicks
    Jun 24, 2022 at 20:05
  • \$\begingroup\$ @chicks Sorry, my bad. Please feel free to remove my question. It seems that I cannot do it by myself. \$\endgroup\$ Jun 24, 2022 at 20:56
  • \$\begingroup\$ I’m voting to close this question because it doesn't look for a review. There's Which computer science / programming Stack Exchange sites do I post on? \$\endgroup\$
    – greybeard
    Jun 26, 2022 at 14:35
  • \$\begingroup\$ @greybeard please close it. Sorry for any inconveniences. \$\endgroup\$ Jun 27, 2022 at 15:05
  • 1
    \$\begingroup\$ I don't see a problem here? if there are recommendations to improve the script is a clear request for a review \$\endgroup\$
    – Reinderien
    Jun 28, 2022 at 19:46

1 Answer 1

2
\$\begingroup\$
set -e # Terminate if script has error

The comment doesn't add value - assume that anybody reading either knows what the flag means or can read the Bash manual.

Consider adding -u and -o pipefail as other useful settings.


printf "
####################################################################
# Enter location on disk for site (Default: /var/www/\$fqn):
####################################################################\n
dir: "
read -rp "" sitedir

That's a bit odd. I recommend using %s rather than interpolating a variable directly into the printf format string. Also, printing a partial line will confuse read (which assumes that it begins at start of line). Even more strangely, you specifically pass -p "". I'd replace that with read -rp 'dir: ' sitedir.


if [ -n "$sitedir" ]; then
    if [ ${sitedir:+1} ]
    then
        echo "yes"
    fi
else
    sitedir="/var/www/$fqn"
fi

What's going on here? Just use the usual parameter defaulting:

echo "sitedir=${sitedir:=/var/www/$fqn}"

# servername
if [ -n "$sub" ]; then
    servername="server_name $sub.$fqn www.$sub.$fqn;"
    vhostserver="ServerName $sub.$fqn
    ServerAlias www.$sub.$fqn"
else
    servername="server_name $fqn www.$fqn;"
    vhostserver="ServerName $fqn
    ServerAlias www.$fqn"
fi

Could be simplified if we create a variable:

hostname=${sub:+$sub.}$fqn
servername="server_name $hostname www.$hostname;"
vhostserver="ServerName $hostname
ServerAlias www.$hostname
\$\endgroup\$
2
  • \$\begingroup\$ By the way, is it possible that there is a typo in the definition of $hostname? I assume it has to be defined as hostname=${sub:+$sub.}$fqn otherwise you get a wrong hostname in case no subdomain is provided by the user. This feature was not meant to be obligatory. \$\endgroup\$ Jun 29, 2022 at 20:20
  • 1
    \$\begingroup\$ Yes, a typo - now fixed. Thank you! \$\endgroup\$ Jun 30, 2022 at 6:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.