From Organic Design wiki

Linode Setup Reference:

Initial Setup

Update And Configure Timezone

apt-get update
apt-get upgrade
dpkg-reconfigure tzdata

Creating A New User

adduser saul # create the user saul
adduser saul sudo # adds saul to the sudo group
sudo usermod -a -G www-data saul # add saul to the www-data group

Setting up Authentication Keys

ssh-keygen -b 4096 # create the keyfile - do this on the client (watch you don't overwrite your existing one if you have done this before!)
ssh-copy-id saul@LINODE_IP # uploads the public key to linode

Configure SSH

sudo nano /etc/ssh/sshd_config
	# Modify these lines to look like this:
	PermitRootLogin no # this stops root from logging in
	PasswordAuthentication no # this stops anyone from logging in without authentication keys
sudo service ssh restart # reboots ssh and applies changes

Setting Up The LAMP Stack

Install And Configure Apache

sudo apt-get install apache2
sudo cp /etc/apache2/apache2.conf /etc/apache2/apache2.backup.conf # backup the configuration file before editing
sudo nano /etc/apache2/apache2.conf
	# Modify this line like so:
	KeepAlive Off # keepalive allows fast connections to those who are already connected but may hold up other clients
	# Append these lines to the end of the file:
	<IfModule mpm_prefork_module>
		StartServers 4
		MinSpareServers 20
		MaxSpareServers 40
		MaxClients 200
		MaxRequestsPerChild 4500
sudo service apache2 restart # restart apache to apply changes

Configure Virtual Hosts For Apache

sudo a2dissite *default # Disable the default Apache virtual host
cd /var/www/
sudo mkdir
sudo mkdir
sudo mkdir
sudo mkdir
sudo nano /etc/apache2/sites-available/
	# domain: 
	# public: /var/www/ 
	<VirtualHost *:80>
		# Admin email, Server Name (domain name), and any aliases
		# Index file and Document Root (where the public files are located) 
		DirectoryIndex index.html index.php
		DocumentRoot /var/www/

		# Log file locations 
		LogLevel warn
		ErrorLog /var/www/
		CustomLog /var/www/ combined
sudo a2ensite # adds a link in the correct location to the configuration file
sudo service apache2 restart
# Rince and repeat for any other websites to host.

# Optional:
#sudo nano /etc/hosts # This is to test the virtual hosts and is done on the local computer
	# Append a line like this

Install And Configure MySQL

sudo apt-get install mysql-server
sudo mysql_secure_installation # set a unique password, remove anonymous user accounts, disable remote root login, and remove the test database
sudo nano /etc/mysql/my.cnf # for optimising mysql
	max_allowed_packet = 1M
sudo service mysql restart

Install And Configure PHP

sudo apt-get install php7.0 php-pear php7.0-mysql # PHP may update - change php7.0 to the current version number
sudo mkdir -p /var/log/php # create the folder for error logging
sudo chown www-data /var/log/php # changes the group of the file we created with the group www-data
sudo service apache2 restart

# linux mint needs this package isntalled to get php working on apache2:
sudo apt-get install libapache2-mod-php5

# Optional install php gd - this is an add-on allows php to manipulate image files - often used by gallery plugins in Wordpress.
sudo apt-get install php7.0-gd



sudo apt-get install nginx
sudo /etc/init.d/nginx start
sudo apt-get install php7.0 php-pear php7.0-mysql php-fpm


sudo nano /etc/nginx/sites-enabled/default

# Basic configuration
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/public_html;

        index index.html index.htm index.nginx-debian.html index.php;

        server_name _;

        # This solves the permalink problem
        if (!-e $request_filename) {
                rewrite ^.*$ /index.php last;

        location / {
                try_files $uri $uri/ /index.php?$args =404;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;

                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

        location ~ /\.ht {
                deny all;

sudo /etc/init.d/nginx reload
sudo nginx -s reload

Setting Up Wordpress

Creating The Database

mysql -u root -p # OR:
sudo mysql --user=root --password="ROOTPASSWORD" # Enter the MYSQL database
	create database; # create the database (can be anything) for wordpress
	create user 'USER' identified by 'PASSWORD'; # create a user by the name of USER with the password PASSWORD
	grant all on* to 'USER' identified by 'PASSWORD'; # grant a user by the name of USER the permissions to modify the database with the password PASSWORD
	quit; # exit mysql


cd /var/www/
sudo rm index.* # move or remove any index.* files
sudo chown -R www-data:www-data /var/www/ # ensure that the files are owned by the webserver
sudo wget # download the latest wordpress
sudo -u www-data tar -xvf latest.tar.gz # extract it
sudo mv latest.tar.gz ../backups/wordpress-`date "+%Y-%m-%d"`.tar.gz # archive the compressed folder OR just delete it
sudo mv wordpress/* ./ # move the files out of the wordpress folder so the site will use them
sudo rm -R wordpress # delete the old wordpress folder

#Go to your domain and follow the instructions for the rest of the installation

See Also

Permalink 404 Error Fix

sudo nano /var/www/ # Create an empty file here and make sure permissions are correct - wordpress should automatically do this if permissions are fine.
sudo nano /etc/apache2/sites-available/
	# Append these lines:
	<Directory /var/www/>
		Options +ExecCGI
		Options Indexes FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all
	<Directory /var/www/>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all


Not Working

php -a # boot php
	mail ('YOUR@EMAIL', "Test Postfix", "Test mail from postfix"); # send test Email
	# Possible error: sh: 1: /usr/sbin/sendmail: not found
	exit # exit php
sudo apt-get install sendmail


Email sending can be real slow if hostnames are not set up correctly, to fix this edit this file:

sudo nano /etc/hosts

Edit the (first?) line that says:    localhost

Change to:    localhost localhost.localdomain

Then reload sendmail by running:

sudo sendmailconfig # Answer yes to all questions


To enable encryption on sendmail run

sudo nano /etc/mail/

And add this line to the end:


Then reload sendmail by running:

sudo sendmailconfig # Answer yes to all questions

Not Sending To Own Domain

If your mail server is a different one from your web server and you try to send mail to your own domain your system may try to handle it internally.
If you test php sendmail and it does this you will see an output like:

/home/user/dead.letter... Saved message in /home/user/dead.letter

To fix simply edit this file:

sudo nano /etc/mail/

and Inject the following lines:

define(`MAIL_HUB', `')dnl
define(`LOCAL_RELAY', `')dnl

Immediately before these lines (near the end)


Then run:

sudo sendmailconfig # Answer yes to all questions

Servers Rejecting Mail

Some servers will reject mail without this defined in /etc/mail/

define(`confDOMAIN_NAME', `')dnl


Setting up muilti-sites Unique domains

Edit the wp-config.php file and add the following lines just above the /* That’s all, stop editing! Happy blogging. */ line:

/* Multisite */
define( 'WP_ALLOW_MULTISITE', true );

Then deactivate all plugins.
Then go to Tools » Network Setup and follow the instructions.

Setting Up SSL

Installing Certbot For Let's Encrypt On Apache

sudo nano /etc/apt/sources.list
	# append to file to enable backports
	deb stretch-backports main
sudo apt-get update # to update the backports
sudo apt-get install python-certbot-apache -t stretch-backports
sudo certbot --authenticator webroot --installer apache

Auto Renew The Certificate

sudo certbot renew --dry-run # test SSL autorenewal
cd /etc/cron.daily
sudo cp dpkg certbot
sudo nano certbot # remove the contents and replace with
	certbot renew --renew-hook "service restart apache2"
sudo run-parts -v /etc/cron.daily # test daily crons

See Also

Installing Certbot For Let's Encrypt On Node.js

Certbot creates files and folders in the root directory for testing, this means node has to be able to get so the root dir has to be set up as a directory.
A repository for a good file to achieve this can be found Here.
If you are using express you may want to add something like this:

app.use('/.well-known', express.static(path.join(__dirname, "/../.well-known/")));

Make sure the node server is running while you run the cert!

sudo nano /etc/apt/sources.list
	# append to file to enable backports
	deb stretch-backports main
sudo apt-get update # to update the backports
sudo apt-get install certbot -t stretch-backports
sudo certbot certonly --webroot -w /var/www/ -d -d

Auto-renew is pretty much the same but without the hook.

See Also

Wildcard Certs (Unfinished)

This does not work on Debian 9 (Yet)

sudo certbot certonly --manual -d *.YOUR_DOMAIN_NAME --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns-01 --server

See also.

Setting Up Git


# Make a repository on github


sudo apt-get install git
git clone # download the repository OR use the alternative at the bottom to create a new repository
git add . # add all files for committing
git commit -am "COMMIT MESSAGE" # commit the changes locally
git push origin master # push changes to the server

# Alternative to git clone:
mkdir repositoryFolder
git init # creates a repository


sudo apt-get install git
cd /to/the/folder/you/would/like/to/have/your/repository # maybe change to wordpress's theme directory?
git clone
sudo nano /somelocation/under/your/domain/fileName.php # Create the file with the contents below:
	if( array_key_exists( 'HTTP_X_HUB_SIGNATURE', $_SERVER ) ) {
		$body = file_get_contents( 'php://input' );
		$hmac = hash_hmac( 'sha1', $body, 'SECRET' );
		if( $sig === "sha1=$hmac" ) {
			$repo = json_decode( $body )->repository->name;
			exec( "cd /PATH/TO/LOCAL/CLONES/$repo && sudo git pull --no-edit" );
sudo visudo # might not be needed?
	# Add this to the end of the file
	# Give www-data permissions to run git pull
	www-data ALL=(ALL) NOPASSWD : /usr/bin/git pull --no-edit
# Check the log under the site folder for php errors


# On github add a webhook under settings, type: json, make sure secret (use a good password) aligns with the script (from on the server), and paste a link to the script url (from on the server)

Setting Up Node.js


curl -sL | sudo -E bash -
sudo apt-get install nodejs
sudo apt-get install build-essential # install the optional add-ons


sudo npm install -g express-generator # install express
express PROJECT_NAME # create an express project called PROJECT_NAME 
npm i # install dependancies

Vue Router

sudo npm install --global vue # install vue globally THIS MIGHT ACTUALLY BE vue-cli
sudo vue init webpack-simple APPNAME # create a new project using the "webpack-simple" template
# Make sure to say yes to vue-router or enter the project directory and install it via "npm i vue-router"
npm i # install dependencies


npm install @feathersjs/cli -g # install feathers globally
mkdir server && cd server
feathers generate app # generate the feathers app

Mail Exim

For a in-depth guide on this please see this
Ensure that you have a reverse DNS setup.

Install And Setup Exim

sudo apt-get install exim4-daemon-heavy dovecot-common dovecot-imapd spamassassin spamc spf-tools-perl # install required packages
dpkg-reconfigure exim4-config
	# no splitting configuration files
	# internet
	# Maildir
	# set your domain and anything else that is obvious
	# everything else default

sudo nano /etc/mailname
	# ensure this is your domain name

hostname -b command YOU_DOMAIN_NAME # set your domain as hostname for mail
sudo /etc/init.d/exim4 restart

If you require multiple domain set up on the one server please refer to this
To test if you have setup this correctly run these commands:

sudo -v 'user@domain'
	(Control D)

cd ~/Maildir/new
	# Check that a file(s) exists -if not check ~/Maildir/cur or another user account.
	# you should see test mail you sent yourself.

Install And Setup SMTP

SMTP Certificates

Lets Encrypt

To setup exim for an existing lets encrypt cert:

certbot certonly --standalone -d -d -d
sudo nano /etc/exim4/exim4.conf.template
	# add these lines to the top of the "main/03_exim4-config_tlsoptions" section
	MAIN_TLS_CERTIFICATE = /etc/letsencrypt/live/YOUDOMAIN/fullchain.pem
	MAIN_TLS_PRIVATEKEY = /etc/letsencrypt/live/YOURDOMAIN/privkey.pem

	# add these lines to the top of the file.
	daemon_smtp_ports = 25 : 2525

sudo /etc/init.d/exim4 restart

The permissions on the following files need read and execute for everyone or the certs cannot be read by exim:

  • /etc
  • /etc/letsencrypt
  • /etc/letsencrypt/live
  • /etc/letsencrypt/live/YOUCERTFOLDER
  • /etc/letsencrypt/archive
  • /etc/letsencrypt/archive/YOURCERTFOLDER
Self Signed

For SSL setup with exim cert (preferable to do it with letsencrypt rather than selfsigned)

sudo /usr/share/doc/exim4-base/examples/exim-gencert # gen a cert
sudo nano /etc/exim4/exim4.conf.template
	# add these lines to the beginning of the file:
	daemon_smtp_ports = 25 : 2525

sudo /etc/init.d/exim4 restart


Accounts will be authenticated with the default linux accounts.

sudo chgrp Debian-exim /etc/shadow # change the group for access
sudo chmod g+r /etc/shadow # and the permissions
sudo nano /etc/exim4/exim4.conf.template
	# find the plain_server section and configure like this:
		driver = plaintext
		public_name = PLAIN
		server_condition = "${if crypteq{$auth3}{${extract{1}{:}{${lookup{$auth2}lsearch{/etc/shadow}{$value}}}}}{1}{0}}"
		server_set_id = $auth2
		server_prompts = :

sudo /etc/init.d/exim4 restart

To test if its working run on the client:

telnet 2525

If this doesn't work check that you have a wildcard (*) record on your DNS.
If it still doesn't work check that the port is open on the server by running:

netstat -nlp

If a result like below is returned the port isn't opened:

tcp        0      0*               LISTEN

To open it run:

sudo cp /etc/exim4/update-exim4.conf.conf /etc/exim4/update-ex
im4.conf.conf.backup # backup before changing
sudo nano /etc/exim4/update-exim4.conf.conf 
	# make this file looks like this:

sudo /etc/init.d/exim4 restart

netstat -nlp # test again

You should see a result like this if the port is opened:

tcp        0      0  *               LISTEN

And this should now work:

telnet 2525

Copy Mail To The Sent Folder

See this as to why.

sudo nano /etc/exim4/exim4.conf.template
	# Add these lines under the other settings at the top:
	system_filter = /var/www/tools/exim-copy-to-sent
	system_filter_directory_transport = copy_to_sent
	system_filter_pipe_transport = copy_to_sent_pipe

	# Insert this after the "end transport/30_exim4-config_mail_spool" header:
		driver = appendfile
		group = Debian-exim
		user = ${if match {${local_part:${lookup{$sender_address_local_part@$sender_address_domain}lsearch*@{/etc/exim4/virtual.users}}}}{(.+)}{$1}{Debian-exim}}
		mode = 0660
		directory = /home/${local_part:${lookup{$sender_address_local_part@$sender_address_domain}lsearch*@{/etc/exim4/virtual.users}}}/Maildir/.Sent/

		driver = pipe
		user = Debian-exim
		group = Debian-exim

sudo nano /var/www/tools/exim-copy-to-sent
	# Add this content:
		$sender_address_local_part is not "root"
		"${if def:h_X-Spam-Status {def}{undef}}" is "undef"
		"${if match {${lookup{$sender_address}lsearch{/etc/exim4/virtual.users}}}{@localhost}{yes}{}}" is "yes"
		unseen save $home/Maildir/.Sent/
		unseen pipe "/var/www/tools/ \"$recipients\""

DoveCot IMAP Server

sudo mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.backup
sudo nano /etc/dovecot/dovecot.conf
	# Create the file with these contents:
	log_path = /var/log/dovecot.log

	protocols = imap

	service imap-login {
		inet_listener imap {
			address = localhost

	mail_location = maildir:~/Maildir
	maildir_very_dirty_syncs = yes

	userdb {
		driver = passwd

	passdb {
		driver = pam

	ssl = required
	ssl_prefer_server_ciphers = yes
	ssl_protocols = !SSLv3 # SSLv2 has been removed from OpenSSL so is not even a valid name to disable
	ssl_cert = </etc/letsencrypt/live/
	ssl_key = </etc/letsencrypt/live/

Spam Assassin

sudo nano /etc/default/spamassassin
	# change the values like so:

sudo service spamassassin start # start spam assassin
sudo nano /etc/exim4/exim4.conf.template 
	# go to the "end router/800_exim4-config_maildrop" section and insert the following lines:
	# 850: Spamcheck router
		condition = "${if and { {!def:h_X-Spam-Flag:} {!eq {$received_protocol}{spam-scanned}}} {1}{0}}"
		driver = accept
		transport = spamcheck_transport

	# add the following lines just BEFORE the section "end transport/30_exim4-config_remote_smtp_smarthost":
	# 30: Spamcheck transport
		debug_print = "T: spamassassin_pipe for $local_part@$domain"
		driver = pipe
		command = /usr/sbin/exim4 -oMr spam-scanned -bS
		transport_filter = /usr/bin/spamc
		home_directory = "/tmp"
		current_directory = "/tmp"
		user = Debian-exim
		group = Debian-exim
		message_prefix =
		message_suffix =

	# go to this section: "600_exim4-config_userforward" and uncomment this line like so (this may already be uncommented:
		debug_print = "R: userforward for $local_part@$domain"
		driver = redirect
		domains = +local_domains
		file = $home/.forward
		require_files = $local_part:$home/.forward
		allow_filter # enusre this line isn't commented out

sudo /etc/init.d/exim4 restart
sudo -u MAILUSER nano .forward # create this file in the home directory of the mail user
	# add the following lines to the fiile BUT not this line:
	# Exim filter
		$h_X-Spam-Status: CONTAINS "Yes"
		"${if def:h_X-Spam-Flag {def}{undef}}" is "def"
		save $home/Maildir/.Trash/


Hotmail Whitelisting

You will have to fill out this to stop hotmail bouncing messages back.

Mail Postfix (Unfinished)

First of all make sure that your DENS servers are working and you have a reverse DNS setup.
Then make sure you get a SSL Cert for the domain.
There are two main mail servers exim and postfix - this setup shows how to use the latter.

Install and setup mail servers:

sudo apt-get install net-tools sendmail dovecot-imapd dovecot-lmtpd postfix postgrey postfix-policyd-spf-python # install mail dependancies
sudo apt-get purge exim4 exim4-* # remove the unnecessary mail packages.
sudo nano /etc/postfix/
	# Uncomment the following line:
	submission inet n       -       -       -       -       smtpd

sudo nano /etc/postfix/
	# change the 2 following lines to the relevant info:
	smtpd_tls_cert_file = /etc/letsencrypt/live/
	smtpd_tls_key_file = /etc/letsencrypt/live/

	# comment out the following line like so:

	# add the following lines:
	smtpd_tls_security_level = may
	smtp_tls_security_level = may

sudo -i # login into root
doveconf -n > /etc/dovecot/
mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
mv /etc/dovecot/ /etc/dovecot/dovecot.conf
nano /etc/dovecot/dovecot.conf
	# comment out the line like so:
	#ssl = no
	# add the following block to the end:
	service imap-login {
		inet_listener imap {
			port = 0
		inet_listener imaps {
			port = 993

	ssl = required

	ssl_cert = </etc/letsencrypt/live/
	ssl_key = </etc/letsencrypt/live/

cp /lib/systemd/system/dovecot.socket /etc/systemd/system/
systemctl reenable dovecot.socket
sed -i '/:143$/s/^/#/' /etc/systemd/system/dovecot.socket
systemctl restart postfix
systemctl restart dovecot
netstat -lnpt # check that the ports 25, 993, and 587 are in the local column
exit # exit root

On your local computer run:

openssl s_client -starttls smtp -crlf -connect <your_mail_server>:587
openssl s_client -connect <your_mail_server>:993

Both of these should return "Verify return code: 0 (ok)" near the end if things are good.


sudo nano /etc/postfix/
	# append this following block to the file:
	smtpd_sasl_auth_enable = yes
	smtpd_sasl_type = dovecot
	# The path is relative to $queue_directory:
	#   # postconf |grep queue_directory
	#   queue_directory = /var/spool/postfix
	smtpd_sasl_path = private/auth
	# Do not accept SASL authentication over unencrypted connections
	smtpd_tls_auth_only = yes

sudo nano /etc/dovecot/dovecot.conf
	# Append the following code:
	# Allows plaintext authentication only when SSL/TLS is used first.
	auth_mechanisms = plain login
	disable_plaintext_auth = yes
	service auth-worker {
		# Forbid to access /etc/shadow
		user = $default_internal_user

	service auth {
		# IMPORTANT: Match the path to smtpd_sasl_path of Postfix
		unix_listener /var/spool/postfix/private/auth {
			group = postfix
			user = postfix
			mode = 0666

	# replace the mail_location varible with this:
	mail_location = maildir:/var/vmail/%d/%n

	# replace the passdb varible with this:
	passdb {
		driver = passwd-file
		# The entire email address will be used as the username for email client.
		# Don't bother about the scheme here, will be overwritten by a strong scheme from file.
		#    (
	args = scheme=CRYPT username_format=%u /etc/dovecot/users

	# replace the userdb varible with this:
	userdb {
		# For static type, LDA verify the user's existence by lookup passdb
		#   ( )
		driver = static
		args = uid=vmail gid=vmail home=/var/vmail/%d/%n

sudo adduser --system --home /var/vmail --uid 550 --group --disabled-login vmail # add the user vmail
doveadm pw -s SHA512-CRYPT # generate a password hash
sudo -i
cat << EOF >> /etc/dovecot/users{SHA512-CRYPT}$6$VaEOV5mzsbP1q2H9$Ctar1HzJCZGXmlXcJDluXEFjGdEwjDKIZ80I0KhG6YD4c2X13YDX/dIb1kGPLAwo7.fTnRaQpcsN5O5O9QjaJ0
chmod 640 /etc/dovecot/users
chown root:dovecot /etc/dovecot/users
systemctl restart postfix
systemctl restart dovecot
ls -l /var/spool/postfix/private/auth # check to see ig a file shows
exit # exit root

On your local machine to test:

openssl s_client -connect <your_mail_server>:993

Mail Delivery

sudo nano /etc/postfix/
	# add these lines at the start
	mydomain = 
	myhostname = mx.$mydomain
	myorigin = $mydomain
	mydestination = localhost

	#Handing off local delivery to Dovecot's LMTP
	# The path relative to $queue_directory, that is:
	#    /var/spool/postfix/private/dovecot-lmtp
	virtual_transport = lmtp:unix:private/dovecot-lmtp

	# Check domains only, query users and aliases in Dovecot
	# IMPORTANT: Don't overlap with $mydestination
	#virtual_mailbox_domains =,
	virtual_mailbox_domains = $mydomain

	#virtual_alias_domains = $virtual_alias_maps
	virtual_alias_maps = hash:/etc/postfix/virtual_aliases

sudo nano /etc/postfix/virtual_aliases
	# insert the following lines into the file: (Replacing the relevant info with yours)
	# The input(left column) without domain, will match user@$myorigin
	#   and user@$mydestination (e.g., root@localhost)
	# The result(right column) without domain, Postfix will append
	#   $myorigin as $append_at_myorigin=yes
	# So the user user1@YOURDOMAIN.ocm must exists in /etc/dovecot/users
	# See: The section TABLE FORMAT in manual virtual(5)

	postmaster          root
	webmaster           root

	# Person who should get root's mail
	root                user1    user1

	# A catch-all address is at the risk of spam       user1

sudo postmap /etc/postfix/virtual_aliases
sudo postfix reload
sudo nano /etc/dovecot/dovecot.conf
	# append the following info:
	service lmtp {
		unix_listener /var/spool/postfix/private/dovecot-lmtp {
			mode = 0666
			user = postfix
			group = postfix

sudo systemctl restart postfix
sudo systemctl restart dovecot
sudo ls -l /var/spool/postfix/private/dovecot-lmtp
sudo sendmail -bv webmaster
sudo ls -l /var/vmail/