Skip to content

Instantly share code, notes, and snippets.

@dkrusky
Last active December 23, 2018 02:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dkrusky/32044293470e799f7dc59d8e96dbba4e to your computer and use it in GitHub Desktop.
Save dkrusky/32044293470e799f7dc59d8e96dbba4e to your computer and use it in GitHub Desktop.
Configure fresh Debian 9 on Linode for hosted site with SSL, firewall, mysql, apache2, and php7.2-fpm
#!/bin/bash
# set the dyndns name you want to allow access from
dyndns="testing.noip.me"
# set the domain you will be using
domain="testing.com"
# set the email address for letsencrypt
email="info@testing.com"
# add repos and update
apt update -q -y && apt upgrade -q -y
apt install apt-transport-https lsb-release ca-certificates -q -y
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
echo "deb http://ftp.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list
echo -e "deb http://repo.mysql.com/apt/debian/ stretch mysql-5.7\ndeb-src http://repo.mysql.com/apt/debian/ stretch mysql-5.7" > /etc/apt/sources.list.d/mysql.list
wget -O /tmp/RPM-GPG-KEY-mysql https://repo.mysql.com/RPM-GPG-KEY-mysql
apt-key add /tmp/RPM-GPG-KEY-mysql
apt update -q -y
# fix vim mouse and syntax
echo "set mouse=
syntax on" > ~/.vimrc
echo "export LS_OPTIONS='--color=auto'
eval \"\`dircolors\`\"
alias ls='ls \$LS_OPTIONS'
alias ll='ls \$LS_OPTIONS -l'
alias l='ls \$LS_OPTIONS -lA'
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
" > ~/.bashrc
# disable interactive so mysql has no root password
export DEBIAN_FRONTEND=noninteractive
# install core server stuff
apt install git lrzsz php7.2-cgi php7.2-cli php7.2-common php7.2-curl php7.2-fpm php7.2-gd php7.2-imap php7.2-json php7.2-mbstring php7.2-mysql php7.2-opcache php7.2-readline php7.2-soap php7.2-xml php7.2-xmlrpc php7.2-zip apache2 apache2-bin apache2-data apache2-suexec-custom apache2-utils libapache2-mod-security2 libapache2-mod-xsendfile sendmail mysql-server net-tools ipset libwww-perl libio-socket-ssl-perl libnet-ssleay-perl libgd-graph-perl unzip -q -y
# enable headers and ssl support in apache
a2enmod headers ssl xsendfile proxy_fcgi
a2enconf php7.2-fpm
service apache2 stop
# disable default site
a2dissite 000-default.conf
# install nodejs
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
#export NVM_DIR="${XDG_CONFIG_HOME/:-$HOME/.}nvm"
#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
#nvm install node 10
# install letsencrypt certbot
apt-get install python-certbot-apache -t stretch-backports -q -y
# configure default domain
echo "
<VirtualHost *:80>
ServerName $domain
ServerAlias www.$domain
ServerAdmin info@$domain
DocumentRoot /var/www/html
ErrorLog \${APACHE_LOG_DIR}/$domain-error.log
CustomLog \${APACHE_LOG_DIR}/$domain-access.log combined
</VirtualHost>" > /etc/apache2/sites-available/$domain.conf
a2ensite $domain.conf
service apache2 start
# install letsencrypt ssl
certbot --apache -d $domain -d www.$domain --email $email
# get ssl key pin
HPKP=`openssl x509 -in /etc/letsencrypt/live/"$domain"/cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64`
echo "
<IfModule mod_ssl.c>
<IfModule mod_headers.c>
Header Always set Strict-Transport-Security \"max-age=31536000; includeSubdomains; preload\" env=HTTPS
Header always set Public-Key-Pins \"pin-sha256=\\\"$HPKP\\\"; max-age=5184000\"
</IfModule>
SSLUseStapling on
SSLStaplingCache \"shmcb:logs/stapling_cache(128000)\"
<VirtualHost *:443>
ServerName $domain
ServerAlias www.$domain
ServerAdmin info@$domain
DocumentRoot /var/www/html
Header set Server \"Microsoft-IIS/4.0\"
XSendFile on
XSendFilePath /var/www/html/downloads/
SetEnv COMPOSER_DISABLE_XDEBUG_WARN 1
<IfModule mod_headers.c>
Header Always set Cache-Control \"max-age=0, no-cache, no-store, must-revalidate\"
Header Always set Pragma \"no-cache\"
</IfModule>
<Directory /var/www/html/>
Options -Indexes +FollowSymLinks
SSLRequireSSL
AllowOverride all
Require all granted
</Directory>
ErrorLog \${APACHE_LOG_DIR}/$domain-error.log
CustomLog \${APACHE_LOG_DIR}/$domain-access.log combined
SSLEngine on
# Intermediate configuration, tweak to your needs
SSLProtocol -all -SSLv3 +TLSv1.2
SSLCipherSuite -LOW:AESGCM:AES:!kRSA:!kPSK:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!ADH
SSLStrictSNIVHostCheck Off
SSLCompression off
SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
SSLHonorCipherOrder on
# Add vhost name to log entries:
LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-agent}i\\\"\" vhost_combined
LogFormat \"%v %h %l %u %t \\\"%r\\\" %>s %b\" vhost_common
# Always ensure Cookies have \"Secure\" set (JAH 2012/1)
#Header edit Set-Cookie (?i)^(.*)(;\\s*secure)??((\\s*;)?(.*)) \"\$1; Secure\$3\$4\"
SSLCertificateFile /etc/letsencrypt/live/$domain/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$domain/privkey.pem
</VirtualHost>
</IfModule>" > /etc/apache2/sites-available/$domain-ssl.conf
a2dissite 000-default-le-ssl.conf
a2ensite $domain-ssl.conf
a2enmod proxy_fcgi xsendfile
service mysql stop
mv /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d.mysqld.original
echo "# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# The MySQL Server configuration file.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0" > /etc/mysql/mysql.conf.d/mysqld.cnf
service mysql start
echo "MySQL Server Password"
mysql -u root -p mysql -e "create database wordpress; update user set Host=\"%\" where User=\"root\"; grant all privileges on *.* to 'root'@'%' with grant option; ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''; flush privileges;"
# get csf firewall
wget https://download.configserver.com/csf.tgz
tar xvf csf.tgz
cd csf
./install.sh
# update system
apt update -q -y && apt upgrade -q -y
# restart services
service php7.2-fpm restart
service mysql restart
service apache2 restart
# download and cleanup latest wordpress
rm -rf /var/www/html/index.html
cd /var/www/html/
wget https://wordpress.org/latest.zip
unzip latest.zip
cp -R wordpress/* .
rm -rf wordpress
rm -rf latest.zip
rm -rf license.txt
rm -rf readme.html
rm -rf wp-config-sample.php
rm -rf wp-content/plugins/akismet
rm -rf wp-content/plugins/hello.php
mkdir wp-content/uploads
chown -R www-data: wp-content/uploads
# create .htaccess for wordpress
echo "Options -Indexes
# block rpc calls
<FilesMatch \"class-wp-xmlrpc-server\\.php|xmlrpc\\.php\">
Deny from all
</FilesMatch>
# enable caching of media
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg \"access 2 week\"
ExpiresByType image/jpeg \"access 2 week\"
ExpiresByType image/gif \"access 2 week\"
ExpiresByType image/png \"access 2 week\"
ExpiresByType text/css \"access 2 week\"
ExpiresByType application/pdf \"access 2 week\"
ExpiresByType text/x-javascript \"access 2 week\"
ExpiresByType application/x-shockwave-flash \"access 2 week\"
ExpiresByType image/x-icon \"access 2 week\"
ExpiresDefault \"access 2 week\"
</IfModule>
# compress images and scripts
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file \\.(html?|txt|css|js|pl)\$
mod_gzip_item_include handler ^cgi-script\$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^system - [L]
RewriteRule ^index\\.php\$ - [L]
# Protect Wordpress Login - specify IP to allow
#RewriteCond %{REQUEST_URI} ^(.*)?wp-login\\.php(.*)\$ [OR]
#RewriteCond %{REQUEST_URI} ^(.*)?wp-admin\$
#RewriteCond %{REMOTE_ADDR} !^123\\.123\\.123\\.74\$
#RewriteRule ^(.*)\$ - [R=403,L]
# Handle robots.txt and sitemap.xml
RewriteRule ^sitemap.xml /sitemap.php?p=xml [L]
RewriteRule ^sitemap.xsl /sitemap.php?p=xsl [L]
RewriteRule ^sitemap.css /sitemap.php?p=css [L]
RewriteRule ^sitemap.js /sitemap.php?p=js [L]
RewriteRule ^robots.txt /sitemap.php?p=txt [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>" > .htaccess
# write wordpress config file
echo "<?php
/* Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') ) { define('ABSPATH', dirname(__FILE__) . '/'); }
/* Debug Mode */
error_reporting(E_ALL);
@ini_set('display_errors', false );
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_LOG', false );
define( 'WP_DEBUG_DISPLAY', false );
define( 'SCRIPT_DEBUG', false );
define( 'SAVEQUERIES', false );
/* set variables */
\$table_prefix = 'wp_';
\$use_ssl = true;
\$wp_content_root = 'wp-content';
\$wp_content_uploads = \"{\$wp_content_root}/uploads\";
\$wp_content_plugins = \"{\$wp_content_root}/plugins\";
\$wp_content_plugins_mu = \"{\$wp_content_root}/plugins\";
\$use_fsoverride = false;
\$use_ftpssh = false;
/* Database */
define( 'DB_NAME', 'wordpress');
define( 'DB_USER', 'root');
define( 'DB_PASSWORD', '');
define( 'DB_HOST', 'localhost');
define( 'DB_CHARSET', 'utf8');
define( 'DB_COLLATE', '');
define( 'CUSTOM_USER_TABLE', \$table_prefix.'users' );
define( 'CUSTOM_USER_META_TABLE', \$table_prefix.'usermeta' );
/* Server and Paths */
define( 'WP_SITEURL', (\$use_ssl ? 'https://' : 'http://') . \$_SERVER['HTTP_HOST'] );
define( 'WP_HOME', WP_SITEURL);
define( 'UPLOADS', \$wp_content_uploads );
define( 'WP_CONTENT_DIR', ABSPATH . \"/{\$wp_content_root}\" );
define( 'WP_CONTENT_URL', WP_SITEURL . \"/{\$wp_content_root}\" );
define( 'WP_PLUGIN_DIR', ABSPATH . \"/{\$wp_content_plugins}\" );
define( 'WP_PLUGIN_URL', WP_SITEURL . \"/{\$wp_content_plugins}\" );
define( 'WPMU_PLUGIN_DIR', ABSPATH . \"/{\$wp_content_plugins_mu}\" );
define( 'WPMU_PLUGIN_URL', WP_SITEURL . \"/{\$wp_content_plugins_mu}\" );
/* Cookies */
define( 'COOKIEPATH', preg_replace( '|https?://[^/]+|i', '', WP_HOME . '/' ) );
define( 'SITECOOKIEPATH', preg_replace( '|https?://[^/]+|i', '', WP_SITEURL . '/' ) );
define( 'ADMIN_COOKIE_PATH', SITECOOKIEPATH . 'wp-admin' );
define( 'PLUGINS_COOKIE_PATH', preg_replace( '|https?://[^/]+|i', '', WP_PLUGIN_URL ) );
/* Restrict External Access */
define( 'WP_HTTP_BLOCK_EXTERNAL', true );
define( 'WP_ACCESSIBLE_HOSTS', \"{\$_SERVER['HTTP_HOST']},*.{\$_SERVER['HTTP_HOST']}\" );
/* Cache */
define( 'WP_CACHE', false );
define( 'WP_CACHE_KEY_SALT', 'salt2018' );
/* Languages */
//define( 'WPLANG', 'en_CA' );
//define( 'WP_LANG_DIR', ABSPATH . '/lang' );
/* Options */
define( 'DISABLE_WP_CRON', true );
define( 'ALTERNATE_WP_CRON', false );
define( 'WP_CRON_LOCK_TIMEOUT', 3600 );
define( 'WP_POST_REVISIONS', 2 );
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );
define( 'EMPTY_TRASH_DAYS', 0 );
define( 'WP_MEMORY_LIMIT', '96M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
define( 'WP_ALLOW_MULTISITE', false );
define( 'AUTOSAVE_INTERVAL', 600 );
define( 'NOBLOGREDIRECT', WP_HOME );
define( 'CONCATENATE_SCRIPTS', true );
define( 'DISALLOW_UNFILTERED_HTML', true );
define( 'IMAGE_EDIT_OVERWRITE', true );
define( 'FORCE_SSL_ADMIN', \$use_ssl );
define( 'WP_ALLOW_REPAIR', true );
define( 'AUTOMATIC_UPDATER_DISABLED', true );
define( 'WP_AUTO_UPDATE_CORE', false );
/* Override File/Dir Permissions */
if( \$use_fsoverride ) {
define( 'FS_CHMOD_DIR', ( 0515 & ~ umask() ) );
define( 'FS_CHMOD_FILE', ( 0404 & ~ umask() ) );
}
/* FTP/SSH OPTIONS */
if( \$use_ftpssh ) {
// direct, ssh2, ftpext, ftpsockets
define( 'FS_METHOD', 'ssh2' );
define( 'FTP_SSL', true );
// hostname:port combo for your SSH/FTP server
define( 'FTP_HOST', '' );
// absolute path to root installation directory
define( 'FTP_BASE', ABSPATH );
define( 'FTP_CONTENT_DIR', WP_CONTENT_DIR );
define( 'FTP_PLUGIN_DIR ', WP_PLUGIN_DIR );
// absolute path to your SSH public key
define( 'FTP_PUBKEY', '../.ssh/id_rsa.pub' );
define( 'FTP_PRIKEY', '../.ssh/id_rsa' );
// either your FTP or SSH username
define( 'FTP_USER', '' );
define( 'FTP_PASS', '' );
}
/* Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');" > wp-config.php
# setup CSF
cp /etc/csf/csf.conf /etc/csf/csf.conf.original
sed -i -E '
s/^(TESTING *= *\")[^\"]*/\1'"0"'/g
s/^(VERBOSE *= *\")[^\"]*/\1'"0"'/g
s/^(SYSLOG *= *\")[^\"]*/\1'"0"'/g
s/^(URLGET *= *\")[^\"]*/\1'"2"'/g
s/^(DYNDNS *= *\")[^\"]*/\1'"300"'/g
s/^(DYNDNS_IGNORE *= *\")[^\"]*/\1'"1"'/g
s/^(UI *= *\")[^\"]*/\1'"0"'/g
s/^(RESTRICT_UI *= *\")[^\"]*/\1'"2"'/g
s/^(RESTRICT_SYSLOG *= *\")[^\"]*/\1'"3"'/g
s/^(LF_SPI *= *\")[^\"]*/\1'"1"'/g
s/^(IPV6 *= *\")[^\"]*/\1'"1"'/g
s/^(TCP_IN *= *\")[^\"]*/\1'"443"'/g
s/^(TCP6_IN *= *\")[^\"]*/\1'"443"'/g
s/^(TCP_OUT *= *\")[^\"]*/\1'"20:65534"'/g
s/^(TCP6_OUT *= *\")[^\"]*/\1'"1:65535"'/g
s/^(UDP_IN *= *\")[^\"]*/\1'""'/g
s/^(UDP6_IN *= *\")[^\"]*/\1'""'/g
s/^(UDP_OUT *= *\")[^\"]*/\1'"20:65534"'/g
s/^(UDP6_OUT *= *\")[^\"]*/\1'"1:65535,9999"'/g
s/^(ICMP_IN *= *\")[^\"]*/\1'"0"'/g
s/^(ICMP_OUT *= *\")[^\"]*/\1'"1"'/g
s/^(IGNORE_ALLOW *= *\")[^\"]*/\1'"0"'/g
s/^(LF_DAEMON *= *\")[^\"]*/\1'"1"'/g
s/^(LF_CSF *= *\")[^\"]*/\1'"1"'/g
s/^(LF_IPSET *= *\")[^\"]*/\1'"1"'/g
s/^(FASTSTART *= *\")[^\"]*/\1'"1"'/g
s/^(SMTP_BLOCK *= *\")[^\"]*/\1'"0"'/g
s/^(SMTP_ALLOWLOCAL *= *\")[^\"]*/\1'"1"'/g
s/^(CC_DENY *= *\")[^\"]*/\1'"AE,AF,AL,AR,AS,AZ,BA,BD,BE,BF,BH,BJ,BN,CI,CL,CN,CO,CS,DJ,EG,EH,ER,ES,ET,FR,GM,GN,GR,GW,HK,IQ,IR,IS,IT,JO,KG,KM,KO,KW,KZ,LB,LY,MC,MK,ML,MR,MV,MY,NE,NG,OM,PA,PE,PH,PK,PL,PS,QA,RS,RU,SA,SD,SG,SK,SL,SN,SO,SY,TD,TH,TJ,TM,TN,TR,UA,UZ,VN,XK,YE,YT"'/g
s/^(CC_ALLOW *= *\")[^\"]*/\1'""'/g
s/^(CC_LOOKUPS *= *\")[^\"]*/\1'"1"'/g
s/^(CC6_LOOKUPS *= *\")[^\"]*/\1'"1"'/g
s/^(IPV6_ICMP_STRICT *= *\")[^\"]*/\1'"1"'/g
' /etc/csf/csf.conf
echo "$dyndns" >> /etc/csf/csf.dyndns
echo "tcp:in:d=443:s=64.41.200.0/24" >> /etc/csf/csf.allow
# stop CSF if started
csf -x
# start CSF with new settings
csf -e
echo "
===========================================
CONGRATULATIONS !!!
-------------------------------------------
MySQL, PHP7.2-FPM, Apache2, and your domain
are all ready to go with a letsencrypt cert
There is no password for MySQL currently,
and remote access is enabled for the dyndns
address you specified:
$dyndns
Make sure to create a database and non root
user in MySQL for Wordpress, then add the
database settings to:
/var/www/html/wp-config.php
If you have a static IP, you can uncomment
the lines pertaining to allowing access to
wp-admin from a specific IP address and
enter it in the format as shown.
After ready to run, open your webbrowser to
https://$domain/wp-admin/install.php
.. so you can finish setting up Wordpress.
===========================================
"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment