Arch Post-Installation Checklist

From The Brainwrecked Wiki
Revision as of 20:25, 29 November 2019 by BrainwreckedTech (talk | contribs) (Let's Encrypt: Removed line about removing lines added by certbot -- certonly takes care of that)
Jump to navigation Jump to search

Passwords & User Creation

Make sure root has a password.

Make sure you have a primary user set up.

sudo useradd -m -u <id -ge 1000> -g users -G wheel,games,video,audio,optical,storage,scanner,power <user>
sudo passwd <user>

/etc/fstab

Time Zone

sudo ln -sf /usr/share/zoneinfo/<region>/<city> /etc/localtime
sudo hwclock --systohc

Localization

Un-comment your desired locales in /etc/locale.conf, then run

sudo locale-gen

If you don't have the default /etc/locale.gen file:

sudo rm /etc/locale.gen
sudo pacman -Syu glibc

Edit /etc/locale.conf as you see fit

/etc/locale.conf
LANG=en_US.UTF-8
LANGUAGE=en_US
LC_COLLATE=POSIX
LC_MESSAGES=C
LC_CTYPE=en_US.UTF-8
LC_NUMERIC=en_US.UTF-8
LC_TIME=en_US.UTF-8
LC_MONETARY=en_US.UTF-8
LC_PAPER=en_US.UTF-8
LC_NAME=en_US.UTF-8
LC_ADDRESS=en_US.UTF-8
LC_TELEPHONE=en_US.UTF-8
LC_MEASUREMENT=en_US.UTF-8
LC_IDENTIFICATION=en_US.UTF-8

Set up /etc/vconsole.conf with a keymap and (optionally) a font:

/etc/vconsole.conf
KEYMAP=us
FONT=Lat2-Terminus16

Initramfs

Look for and edit the following lines:

/etc/mkinitcpio.conf
MODULES=([amdgpu|bochs_drm|cirrus|i915|nouveau|(nvidia nvidia_modeset nvidia_uvm nvidia_drm)] [ehci_pci usb_storage]>)
HOOKS=(base udev autodetect modconf block [zfs] filesystems keyboard fsck [encrypt] keymap consolefont)
COMPRESSION=lz4

Networking

Host Name

Make sure a host name is set in /etc/hostname

Then edit /etc/hosts

/etc/hosts
127.0.0.1	localhost.localdomain	locahost
::1		localhost.localdomain	localhost
127.0.1.1	<hostname>.localdomain	<hostname>

Static Addressing

Use systemd-networkd when a machine will use a static address without consulting a DHCP server.

IPv4 Only

/etc/systemd/network/network.network
[Match]
MACAddress=<mac-address>
[Network]
Address=<ipv4-address>/<mask>
DNS=<ipv4-address>
Gateway=<ipv4-address>
LinkLocalAddressing=no
IPv6AcceptRA=no

IPv4 & IPv6

/etc/systemd/network/network.network
[Match]
MACAddress=<mac-address>
[Network]
Address=<ipv6-address>/<mask>
DNS=<ipv6-address>
Gateway=<ipv6-address>
Address=<ipv4-address>/<mask>
DNS=<ipv4-address>
Gateway=<ipv4-address>

Dynamic Addressing

It's preferable to use connman or Network Manager for dynamic addresses as systemd-networkd doesn't play well with interfaces coming and going.

If you'd rather use systemd-networkd for DHCP:

/etc/systemd/network/network.network
[Match]
MACAddress=<mac-address>
[Network]
DHCP=yes
[DHCP]
UseMTU=true

IPTables Firewall

/etc/iptables/iptables.rules
# SIMPLE STATEFUL FIREWALL
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [100308:88697975]
:TCP - [0:0]
:UDP - [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable

# OPEN NECESSARY PORTS HERE
-A TCP -p tcp -m tcp --dport 22 -j ACCEPT -m comment --comment "ssh"
-A TCP -p tcp -m tcp --dport 80 -j ACCEPT -m comment --comment "http"
-A TCP -p tcp -m tcp --dport 443 -j ACCEPT -m comment --comment "https"
-A TCP -p tcp -m tcp --dport 465 -j ACCEPT -m comment --comment "smtps"
-A TCP -p tcp -m tcp --dport 587 -j ACCEPT -m comment --comment "starttls"
-A TCP -p tcp -m tcp --dport 873 -j ACCEPT  -m comment --comment "rsync"
-A TCP -p tcp -m tcp --dport 993 -j ACCEPT -m comment --comment "imaps"
-A TCP -p tcp -m tcp --dport 64738 -j ACCEPT -m comment --comment "mumble tcp"
-A UDP -p udp -m udp --dport 64738 -j ACCEPT -m comment --comment "mumble udp"
COMMIT

Packages

Mirror List

Install and use reflector to automate the use and selection of mirrors.

sudo pacman -Syu reflector
sudo reflector -c <country> -p https -l 5 --sort rate --save /etc/pacman.d/mirrorlist

Yay

Make sure you have the base-devel group installed.

sudo pacman -Syu --needed base-devel

Create a directory where AUR build files will go

sudo mkdir /var/lib/pacman/aur
sudo chmod 1777 /var/lib/pacman/aur
$ mkdir /var/lib/pacman/aur/$(whoami)

Build yay.

$ cd /var/lib/pacman/aur/$(whoami)
$ git clone https://aur.archlinux.org/yay.git
$ cd yay
$ makepkg -Ccisr

The create the configuration file.

~/.config/yay/config.json
{
	"aururl": "https://aur.archlinux.org",
	"buildDir": "/var/lib/pacman/aur/$USER",
	"editor": "nano",
	"editorflags": "",
	"makepkgbin": "makepkg",
	"makepkgconf": "",
	"pacmanbin": "pacman",
	"pacmanconf": "/etc/pacman.conf",
	"tarbin": "bsdtar",
	"redownload": "no",
	"rebuild": "no",
	"answerclean": "none",
	"answerdiff": "all",
	"answeredit": "",
	"answerupgrade": "0",
	"gitbin": "git",
	"gpgbin": "gpg",
	"gpgflags": "",
	"mflags": "",
	"sortby": "votes",
	"gitflags": "",
	"removemake": "yes",
	"requestsplitn": 150,
	"sortmode": 0,
	"completionrefreshtime": 7,
	"sudoloop": false,
	"timeupdate": false,
	"devel": true,
	"cleanAfter": false,
	"gitclone": true,
	"provides": true,
	"pgpfetch": true,
	"upgrademenu": true,
	"cleanmenu": true,
	"diffmenu": true,
	"editmenu": true,
	"combinedupgrade": false,
	"useask": false
}

General Utilities

These packages supplement the base system.

yay -Syu --needed dnsutils fail2ban git haveged hddtemp htop lm_sensors lynx nano ncdu openssh pacman-contrib pkgfile polkit ranger rsync sudo unzip vim w3m whois

The havaged service can be enabled and started right away as no configuration is needed.

sudo systemctl enable --now haveged

Your sensors need to be configured before starting the lm_sensors service.

Note: This package is useless inside a VM.
sudo sensors-detect
sudo systemctl enable --now lm_sensors

Don't start fail2ban or sshd quite yet as they have configuration that needs to be done.

Docker

This is only needed for OnlyOffice

yay -Syu docker
sudo systemctl enable --now docker

E-mail

These packages are needed to set up your own e-mail service.

yay -Syu --needed dovecot mutt pigeonhole postfix redis rspamd swaks

Nginx Web Server and Reverse Proxy

These packages are typically used when setting up web applications like MediaWiki or Wordpress.

yay -Syu --needed apache-tools ca-certificates certbot-nginx mariadb nginx php-{acpu,fpm,gd,imagick,intl,redis,sodium} postgresql

Fail2Ban

/etc/fail2ban/jail.local
[DEFAULT]
bantime         = 1d
destemail       = root@bwt.com.de
sender          = fail2ban@bwt.com.de
action          = %(action_mwl)s
/etc/fail2ban/jail.d/sshd.local
[sshd]
enabled         = true
filter          = sshd
banaction       = iptables
backend         = systemd
maxretry        = 5
findtime        = 1d
bantime         = 2w
ignoreip        = 127.0.0.1/8
/etc/fail2ban/jail.d/postfix-ispmail.local
[postfix-ispmail]
enabled         = true
filter          = postfix
port            = smtp,submission
banaction       = iptables
backend         = systemd
maxretry        = 5
findtime        = 1d
bantime         = 2w
ignoreip        = 192.99.246.231/32
/etc/fail2ban/filter.d/postfix-ispmail.conf
[INCLUDES]
before = common.conf

[Definition]
_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds]
failregex = ^%(__prefix_line)slost connection after AUTH from \S+\[<HOST>\]$
ignoreregex =

[Init]
journalmatch = _SYSTEMD_UNIT=postfix.service

SSH Setup

Look for and edit the following lines in /etc/ssh/sshd_config:

/etc/ssh/ssd_config
Port <port>
AddressFamily <any|inet|inet6>
ListenAddress <ip4-address>
ListenAddress <ip6-address>

LogLevel VERBOSE

PermitRootLogin prohibit-password

PubkeyAuthentication yes

PasswordAuthentication no
PermitEmptyPasswords no

ChallengeResponseAuthentication no

UsePAM yes
AllowUsers <space-separated-list-of-users>

AllowAgentForwarding no
AllowTcpForwarding no

Then make sure the sshd service is enabled and running:

sudo systemctl enable --now sshd

MariaDB

Data Directory

If you want to put your databases somewhere other than the default /var/lib/mysql:

sudo mkdir <dir>
sudo chown -R mysql:mysql <dir>

tmpfs for tmpdir

The directory used by MariaDB for storing temporary files is named tmpdir. For example, it is used to perform disk based large sorts, as well as for internal and explicit temporary tables.

Create the directory with appropriate permissions:

sudo mkdir -pv <dir>
sudo chown mysql:mysql <dir>

Find the id and gid of the mysql user and group:

$ id mysql
uid=27(mysql) gid=27(mysql) groups=27(mysql)

Add to your /etc/fstab file.

/etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir>           <type>  <options>                                       <dump> <pass>
...
tmpfs           /srv/sqltmp     tmpfs   rw,noatime,gid=933,uid=993,size=100M,mode=0750  0 0
...

Configuration File

/etc/my.cnf.d/server.cnf
...
[mysqld]
...
datadir=<dir>
skip-networking
tmpdir=<dir>
...

Initial Setup

sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=<dir>

Start the Server

sudo systemctl enable --now mariadb

Security Settings

sudo mysql_secure_installation

Create a user:

$ mysql -u root -p
MariaDB> CREATE USER '<user>'@'localhost' IDENTIFIED BY '<password>';
MariaDB> GRANT ALL PRIVILEGES ON <database>.* TO '<USER>'@'localhost';
MariaDB> FLUSH PRIVILEGES;
MariaDB> quit

Redis

Redis is used by several programs, including rspamd (e-mail), NextCloud, OnlyOffice, and Wordpress (optional).

By default, redis is already configured to bind to 127.0.0.1:6379, but the Unix socket is not enabled.

/etc/redis.conf
...
# Unix socket.
#
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
unixsocket /run/redis/redis.sock
unixsocketperm 700
...

You will also need to add users to the redis group so processes can access redis

for username in http rspamd; do sudo usermod -G redis ${username}; done

E-mail

Postfix

Create MariaDB Databases for Postfix

CREATE DATABASE MAIL;
CREATE USER 'mailuser'@'localhost' IDENTIFIED BY '<password>';
CREATE USER 'mailadmin'@'localhost' IDENTIFIED BY '<password>';
GRANT ALL ON MAIL.* TO 'mailadmin'@'localhost';
GRANT SELECT ON MAIL.* TO 'mailuser'@'localhost';
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_domains` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_users` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(150) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `MAIL`.`virtual_aliases` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Configure Postfix to Use MariaDB

/etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT 1 FROM virtual_domains WHERE name='%s'
/etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT 1 FROM virtual_users WHERE email='%s'
/etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT destination FROM virtual_aliases WHERE source='%s'
/etc/postfix/mysql-email2email.cf
user = mailuser
password = <password>
hosts = unix:/run/mysqld/mysqld.sock
dbname = MAIL
query = SELECT email FROM virtual_users WHERE email='%s'
sudo postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
sudo postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
sudo postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf,mysql:/etc/postfix/mysql-email2email.cf
sudo chgrp postfix /etc/postfix/mysql-*.cf
sudo chmod 640 /etc/postfix/mysql-*.cf

Dovecot

sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /srv/imap -m
sudo chown -R vmail:vmail /srv/mail
sudo cp /usr/share/doc/dovecot/example-config/dovecot.conf /etc/dovecot
sudo cp -r /usr/share/doc/dovecot/example-config/conf.d /etc/dovecot
/etc/dovecot/conf.d/10-auth.conf
...
#!include auth-system.conf.ext
!include auth-sql.conf.ext
...
/etc/dovecot/conf.d/auth-sql.conf.ext
...
#userdb {
#  driver = sql
#  args = /etc/dovecot/dovecot-sql.conf.ext
#}
...
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/srv/mail/%n@%d
}
/etc/dovecot/conf.d/10-mail.conf
...
mail_location = maildir:/srv/mail/%n@%d/Maildir
...
namespace inbox {
...
  separator = .
...
}
...
/etc/dovecot/conf.d/10-master.conf
...
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
  }
...
}
...
service auth {
...
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
...
}
...
/etc/dovecot/conf.d/10-ssl.conf
...
ssl = yes
...
ssl_cert = </etc/letsencrypt/live/<domain>/fullchain.pem
ssl_key = </etc/letsencrypt/live/<domain>/privkey.pem
...
ssl_ca = </etc/letsencrypt/live/<domain>/chain.pem
...
/etc/dovecot/conf.d/15-mailboxes.conf
...
mailbox INBOX.Drafts {
  special_use = \Drafts
  auto = subscribe
}
mailbox INBOX.Junk {
  special_use = \Junk
  auto = subscribe
  autoexpunge = 30d
}
mailbox INBOX.Trash {
  special_use = \Trash
  auto = subscribe
  autoexpunge = 30d
}
mailbox INBOX.Sent {
  special_use = \Sent
  auto = subscribe
}
...
/etc/dovecot/conf.d/20-imap.conf
...
protocol imap {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins imap_sieve
...
}
/etc/dovecot/conf.d/20-lmtp.conf
...
protocol lmtp {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve
...
}
/etc/dovecot/conf.d/90-sieve.conf
...
sieve_after = /etc/dovecot/sieve-after
...
sieve_plugins = sieve_imapsieve sieve_extprograms
...
# From elsewhere to Junk folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/etc/dovecot/sieve/learn-spam.sieve

# From Junk folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/etc/dovecot/sieve/learn-ham.sieve

sieve_pipe_bin_dir = /etc/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe
}
sudo mkdir /etc/dovecot/sieve{-after,}
/etc/dovecot/sieve-after/spam-to-folder.sieve
require ["fileinto","mailbox"];

if header :contains "X-Spam" "Yes" {
 fileinto :create "INBOX.Junk";
 stop;
}
/etc/dovecot/sieve/learn-spam.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-spam.sh";
/etc/dovecot/sieve/learn-ham.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-ham.sh";
/etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=/run/mysqld/mysqld.sock dbname=MAIL user=mailuser password=<password>
default_pass_scheme = SHA256-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
/etc/dovecot/sieve/rspamd-learn-spam.sh
#!/usr/bin/env sh
exec /usr/bin/rspamc learn_spam
/etc/dovecot/sieve/rspamd-learn-ham.sh
#!/usr/bin/env sh
exec /usr/bin/rspamc learn_ham
sudo sievec /etc/dovecot/sieve-after/spam-to-folder.sieve
sudo sievec /etc/dovecot/sieve/learn-spam.sieve
sudo sievec /etc/dovecot/sieve/learn-ham.sieve
sudo chown root:root /etc/dovecot/dovecot-sql.conf.ext
sudo chmod 600 /etc/dovecot/dovecot-sql.conf.ext
sudo chmod 600 /etc/dovecot/sieve/learn-{spam,ham}.sieve
sudo chmod 700 /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh
sudo chown vmail:vmail /etc/dovecot/sieve/learn-{spam,ham}.sieve
sudo chown vmail:vmail /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh

Final Postfix Configuration

sudo postconf virtual_transport=lmtp:unix:private/dovecot-lmtp
sudo postconf smtpd_sasl_type=dovecot
sudo postconf smtpd_sasl_path=private/auth
sudo postconf smtpd_sasl_auth_enable=yes
sudo postconf smtpd_tls_security_level=may
sudo postconf smtpd_tls_auth_only=yes
sudo postconf smtpd_tls_cert_file=/etc/letsencrypt/live/<domain>/fullchain.pem
sudo postconf smtpd_tls_key_file=/etc/letsencrypt/live/<domain>/privkey.pem
sudo postconf smtp_tls_security_level=may
sudo postconf smtpd_milters=inet:127.0.0.1:11332
sudo postconf non_smtpd_milters=inet:127.0.0.1:11332
sudo postconf milter_protocol=6
sudo postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"
/etc/postfix/master.cf
...
submission inet n - - - - smtpd
 -o syslog_name=postfix/submission
 -o smtpd_tls_security_level=encrypt
 -o smtpd_sasl_auth_enable=yes
 -o smtpd_sasl_type=dovecot
 -o smtpd_sasl_path=private/auth
 -o smtpd_sasl_security_options=noanonymous
 -o smtpd_sender_login_maps=mysql:/etc/postfix/mysql-email2email.cf
 -o smtpd_sender_restrictions=reject_sender_login_mismatch
 -o smtpd_sasl_local_domain=$myhostname
 -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
...

Rspamd

sudo mkdir /etc/rspamd/conf.d
/etc/rspamd/override.d/milter_headers.conf
extended_spam_headers = true;
/etc/rspamd/override.d/classifier-bayes.conf
autolearn = true;
sudo mkdir /var/lib/rspamd/dkim
sudo chown _rspamd:_rspamd /var/lib/rspamd/dkim
sudo rspamadm dkim_keygen -d <domain> -s $(date +%Y%m%d)
/var/lib/rspamd/dkim/<domain>.YYYYMMDD.key
-----BEGIN PRIVATE KEY-----
[base64 key]
-----END PRIVATE KEY-----

The second part of the output of rspamadm dkim_keygen can be directly pasted into a DNS zone if you are running your own server. If you only have control of your domain name through a 3rd party:

Type Host Value
TXT @ v=spf1 a mx ip4:<ip-address> -all
TXT _dmarc v=DMARC1; p=none; sp=none; rf=afrf; pct=100; ri=86400
TXT YYYYMMDD._domainkey v=DKIM1; k=rsa; p=AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789...
/etc/rspamd/local.d/dkim_signing.conf
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
/etc/rspamd/dkim_selectors.map
<domain> YYYYMMDD
sudo systemctl enable --now rspamd

PHP-FPM

Look for and edit the following lines in /etc/php/php.ini:

/etc/php/php.ini
[PHP]
open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/dev/urandom:/proc/meminfo
include_path = ".:/php/includes:/usr/share/pear"
upload_max_filesize = 8M

extension=apcu
extension=dba
extension=exif
extension=gd
extension=gettext
extension=iconv
extension=imagick
extension=imap
extension=intl
extension=sodium
extension=ldap
extension=mysqli
zend_extension=opcache
extension=pdo_mysql
extension=soap

[Date]
date.timezone = <timezone>

[Pdo_mysql]
pdo_mysql.cache_size = 2000

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=1
opcache.save_comments=1

tmpfs for PHP Sessions

/etc/php/php.ini
[Session]
session.save_path = "/srv/phptmp"

Find the id and gid of the mysql user and group:

$ id mysql
uid=27(mysql) gid=27(mysql) groups=27(mysql)
/etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir>           <type>  <options>                                       <dump> <pass>
...
tmpfs		/srv/phptmp	tmpfs	rw,noatime,gid=27,uid=27,size=100M,mode=0750	0 0
...

Then make sure the php-fpm service is enabled and running:

sudo systemctl enable --now php-fpm

Nginx

Main Configuration

First, edit /etc/nginx/nginx.conf:

/etc/nginx/nginx.conf
user					http;
worker_processes			1;	# one(1) worker or equal the number of _real_ cpu cores. 4=4 core cpu
worker_priority				15;	# renice workers to reduce priority compared to system processes for machine health.	Worst case nginx will get ~25% system resources at nice=15
worker_rlimit_nofile			1024;	# maximum number of open files
worker_cpu_affinity			auto;

events {
	multi_accept			on;
	accept_mutex			on;	# serially accept() connections and pass to workers, efficient if workers gt 1
	accept_mutex_delay		500ms;	# worker process will accept mutex after this delay if not assigned. (default 500ms)
	worker_connections		1024;	# number of parallel or concurrent connections per worker_processes
}

http {
	charset				utf-8;
	aio				on;	# asynchronous file I/O, fast with ZFS, make sure sendfile=off
	sendfile			off;	# on for decent direct disk IO, off for VMs
	tcp_nopush			off;	# turning on requires sendfile=on
	tcp_nodelay			on;	# Nagle buffering algorithm, used for keepalive only
	server_tokens			off;	# version number in error pages
	log_not_found			off;
	types_hash_max_size		4096;

	# MIME
	include				mime.types;
	default_type			application/octet-stream;

	# logging
	access_log			/var/log/nginx/access.log;
	error_log			/var/log/nginx/error.log warn;

	# SSL
	ssl_session_timeout             1440m;
	ssl_session_cache		shared:le_nginx_SSL:10m;
	ssl_session_tickets		off;
	ssl_prefer_server_ciphers	off;

	# Diffie-Hellman parameter for DHE ciphersuites
	ssl_dhparam                     /etc/letsencrypt/ssl-dhparams.pem;

	# Mozilla Intermediate configuration
	ssl_protocols			TLSv1.2 TLSv1.3;
	ssl_ciphers			ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

	# OCSP Stapling
	ssl_stapling			on;
	ssl_stapling_verify		on;
	resolver			1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
	resolver_timeout		2s;

	# Size Limits
	#client_body_buffer_size	8k;
	#client_header_buffer_size	1k;
	client_max_body_size		16M;
	#large_client_header_buffers	4 4k/8k;

	## From StackOverflow: for "upstream sent too big header while reading response header from upstream"
	fastcgi_buffers			8 16k;
	fastcgi_buffer_size		32k;

	# Timeouts, do not keep connections open longer then necessary to reduce
	# resource usage and deny Slowloris type attacks.
	client_body_timeout		5s;	# maximum time between packets the client can pause when sending nginx any data
	client_header_timeout		5s;	# maximum time the client has to send the entire header to nginx
	keepalive_timeout		75s;	# timeout which a single keep-alive client connection will stay open
	send_timeout			15s;	# maximum time between packets nginx is allowed to pause when sending the client data

	## General Options

	gzip				off;	# disable on the fly gzip compression due to higher latency, only use gzip_static
	#gzip_http_version		1.0;	# serve gzipped content to all clients including HTTP/1.0
	gzip_static			on;	# precompress content (gzip -9) with an external script
	#gzip_vary			on;	# send response header "Vary: Accept-Encoding"
	gzip_proxied			any;	# allows compressed responses for any request even from proxies
	ignore_invalid_headers		on;
	keepalive_requests		50;	# number of requests per connection, does not affect SPDY
	keepalive_disable		none;	# allow all browsers to use keepalive connections
	max_ranges			1;	# allow a single range header for resumed downloads and to stop large range header DoS attacks
	msie_padding			off;
	open_file_cache			max=1000 inactive=2h;
	open_file_cache_errors		on;
	open_file_cache_min_uses	1;
	open_file_cache_valid		1h;
	output_buffers			1 512;
	postpone_output			1440;	# postpone sends to match our machine's MSS
	read_ahead			512K;	# kernel read head set to the output_buffers
	recursive_error_pages		on;
	reset_timedout_connection	on;	# reset timed out connections freeing ram
	server_name_in_redirect		off;	# if off, nginx will use the requested Host header
	source_charset			utf-8;	# same value as "charset"

	## Request limits
	limit_req_zone	$binary_remote_addr	zone=gulag:1m	rate=60r/m;

	## Log Format
	log_format	main	'$remote_addr $host $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $ssl_cipher $request_time';

	# load configs
	include	/etc/nginx/conf.d/*.conf;
	include	/etc/nginx/sites-enabled/*.conf;

}

Make the sites-available and sites-enabled directories.

sudo mkdir /etc/nginx/sites-{available,enabled}

Non-Proxied Web Site

Create a Web Root

sudo mkdir /srv/http/<domain>

If you don't have anything to put into the directory for the site, create an index.php file.

/srv/http/<domain>/index.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Server Configuration Confirmation</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>

<body style="font-family:sans-serif;">

<h1>Web server is properly configured!</h1>

<?php echo "<h2>PHP is properly configured as well!</h2>" ?>

<p>Time to add some content!</p>

</body>
</html>

Configuration

Make a site available by creating /etc/nginx/sites-available/<domain>.conf:

Note: Remove the #s after setting up Let's Encrypt.
/etc/nginx/sites-available/<domain>.conf
server {
	listen			80;
	listen			[::]:80;
	server_name		<domain>;

#	return 301		https://$host$request_uri;
#
#}
#
#server {
#
#	listen				443 ssl http2;
#	listen				[::]:443 ssl http2;
#	server_name			<domain>;
#
#	ssl_certificate			/etc/letsencrypt/live/<domain>/fullchain.pem;
#	ssl_certificate_key		/etc/letsencrypt/live/<domain>/privkey.pem;
#	ssl_trusted_certificate		/etc/letsencrypt/live/<domain>/chain.pem;
#
#	fastcgi_param			HTTPS on;
#
#	add_header			Strict-Transport-Security max-age=15768000;

	root				/srv/http/$host;
	index				index.html index.php;

	add_header			Cache-Control "public";
	add_header			X-Frame-Options "DENY";

	access_log			/var/log/nginx/access.log main buffer=32k;
	error_log			/var/log/nginx/error.log error;
	limit_req			zone=gulag burst=200 nodelay;

	# ACME challenge
	location ^~ /.well-known {
		allow			all;
		alias			/var/lib/letsencrypt/$host/.well-known;
		default_type		"text/plain";
		try_files		$uri =404;
	}

	location / {
		try_files		$uri $uri/ /index.php?$query_string;
	}

	location ~ /(data|conf|bin|inc)/ {
		deny			all;
	}

	location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
		access_log		off;
		expires			max;
	}

	location ~ \.php$ {
		try_files		$uri = 404;
		fastcgi_pass		unix:/run/php-fpm/php-fpm.sock;
		fastcgi_index		index.php;
		include			fastcgi.conf;
	}

	location ~ /\.ht {
		deny			all;
	}

}

Proxied Web Site

Rspamd comes with its own web interface. Configuring it is similar to a non-proxied site.

Note: Remove the #s after setting up Let's Encrypt.
/etc/nginx/sites-available/<domain>.conf
server {
	listen			80;
	listen			[::]:80;
	server_name		<domain>;

#	return 301		https://$host$request_uri;
#
#}
#
#server {
#
#	listen				443 ssl http2;
#	listen				[::]:443 ssl http2;
#	server_name			<domain>;
#
#	ssl_certificate			/etc/letsencrypt/live/<domain>/fullchain.pem;
#	ssl_certificate_key		/etc/letsencrypt/live/<domain>/privkey.pem;
#	ssl_trusted_certificate		/etc/letsencrypt/live/<domain>/chain.pem;
#
#	add_header			Strict-Transport-Security max-age=15768000;

	root				/srv/http/$host;
	index				index.html index.php;

	add_header			Cache-Control "public";
	add_header			X-Frame-Options "DENY";

	access_log			/var/log/nginx/access.log main buffer=32k;
	error_log			/var/log/nginx/error.log error;
	limit_req			zone=gulag burst=200 nodelay;

	# ACME challenge
	location ^~ /.well-known {
		allow			all;
		alias			/var/lib/letsencrypt/$host/.well-known;
		default_type		"text/plain";
		try_files		$uri =404;
	}

	location / {
		proxy_pass		http://localhost:11334;

		proxy_set_header	Upgrade $http_upgrade;
		proxy_set_header	Connection "upgrade";
		proxy_set_header	X-Real-IP $remote_addr;
		proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header	Host $http_host;
		proxy_set_header	X-NginX-Proxy true;

		auth_basic		"Username and Password Required";
		auth_basic_user_file	/etc/nginx/sites-enabled/htpasswd;
	}

}

Basic Authorization

Some proxied sites have inadequate or no authentication method of their own. Sometimes you may wish to provide an additional "bump in the road" for site access. Nginx can use an htpasswd file to control access.

sudo htpasswd -c /etc/nginx/sites-enabled/htpasswd <user>

You will then be prompted for a password, and to confirm the password. When finished, htpasswd will create the file.

/etc/nginx/sites-enabled/htpasswd
<username>:<password-hash>
Tip: To update the file with more users or new passwords, omit the -c parameter.

To use Basic Authentication with nginx, put these lines in the appropriate server or location stanzas:

	auth_basic		"Restricted By Username and Password";
	auth_basic_user_file	/etc/nginx/sites-enabled/htpasswd;

SSL Certificate Authorization

/etc/ssl/openssl.cnf
[ CA_default ]

dir		= /srv/ssl		# Where everything is kept

certificate	= $dir/ca.cert		# The CA certificate

private_key	= $dir/private/ca.key	# The private key
RNDFILE		= $dir/private/.rand	# The private random number file

default_md	= sha1			# use public key default MD
sudo mkdir -p /etc/ssl/ca/{certs,crl,private}
sudo mkdir /etc/ssl/ca/ca/certs/users
sudo touch /etc/ssl/ca/index.txt
echo ’01’ | sudo tee /etc/ssl/ca/crlnumber

Create the server certificates

sudo openssl genrsa -des3 -out /srv/ssl/ca/private/ca.key 4096
sudo openssl req -new -x509 -days 1095 -key /srv/ssl/ca/private/ca.key -out /etc/ssl/ca/certs/ca.crt
sudo openssl ca -name CA_default -gencrl -keyfile /srv/ssl/ca/private/ca.key -cert /srv/ssl/ca/certs/ca.crt -out /srv/ssl/ca/private/ca.crl -crldays 1095

Create user certificates

Tip: The first two password prompts will be for the signing request. The final one will be for the password for the certificate the user will import into their browser.
Tip: You can put this into a script to make it easier to issue new certificates.
sudo export USRCRTDR="/srv/ssl/ca/certs/users"
sudo openssl genrsa -des3 -out ${USRCRTDR}/USERNAME.key 1024
sudo openssl req -new -key ${USRCRTDR}/USERNAME.key -out ${USRCRTDR}/USERNAME.csr
sudo openssl x509 -req -days 1095 -in ${USRCRTDR}/USERNAME.csr -CA /etc/ssl/ca/certs/ca.crt -CAkey /etc/ssl/ca/private/ca.key -CAserial /etc/ssl/ca/serial -CAcreateserial -out ${USRCRTDR}/USERNAME.crt
sudo openssl pkcs12 -export -clcerts -in ${USRCRTDR}/USERNAME.crt -inkey ${USRCRTDR}/USERNAME.key -out ${USRCRTDR}/USERNAME.p12

If you want to use certificate authorization in nginx, add these lines to the appropriate server stanzas:

	ssl_client_certificate		/srv/ssl/certs/ca.crt;
	ssl_crl				/srv/ssl/private/ca.crl;
	ssl_verify_client		on;

Finalization

Enable your sites by using symlinks in sites-enabled that point to the conf files in sites-available

sudo ln -s ../sites-available/<domain>.conf /etc/nginx/sites-enabled/<domain>.conf

Then make sure the nginx service is enabled and running:

sudo systemctl enable --now nginx

Let's Encrypt

sudo certbot certonly --nginx --agree-tos

The nginx plugin will guide you and help automate the process.

Take note that the nginx configuration in this guide takes into account the settings suggested by certbot.

Automatic Renewal

These will automatically renew your certificates every 2 months on the first Sunday of the month at 4am.

Systemd

/etc/systemd/system/certbot.service
[Unit]
Description=Let's Encrypt renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --agree-tos --no-redirect
/etc/systemd/system/certbot.timer
[Unit]
Description=Bi-monthly renewal of Let's Encrypt's certificates

[Timer]
OnCalendar=Sun *-1,3,5,7,9,11-1..7 04:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target

Crontab

0 4 1-7 2-12/2 0 /usr/bin/certbot renew --agree-tos --no-redirect

ISPmail Admin

Start with the Non-Proxied Web Site template.

Then, in the site's root:

sudo wget 'https://www.ima.jungclaussen.com/dwn/dwn.php?v=0.9.6&f=.tar.gz'
sudo tar -xvf 'dwn.php?v=0.9.6&f=.tar.gz'
sudo mv ispmail*/htdocs/* ./
sudo rm -r 'dwn.php?v=0.9.6&f=.tar.gz' ispmail*

Then edit the configuration file:

<ispmail_root>/cfg/config.inc.php
<?php

// @package     ISPmail_Admin
// @author      Ole Jungclaussen
// @version     0.9.0

// SHOW PHP ERRORS (DEBUGGING)

// ini_set('display_startup_errors', 1);
// ini_set('display_errors', 1);
// error_reporting(-1);

// DATABASE ACCESS

define('IMA_CFG_DB_HOST',       '127.0.0.1');
define('IMA_CFG_DB_PORT',       '3306');
define('IMA_CFG_DB_USER',       'mailadmin');
define('IMA_CFG_DB_PASSWORD',   '<password>');
define('IMA_CFG_DB_DATABASE',   'MAIL');

// PASSWORD HASHES (enable only *one*)

define('IMA_CFG_USE_SHA256_HASHES', true);
// define('IMA_CFG_USE_MD5_HASHES', true);

// ACCESS CONTROL: uncomment the type you want to use.

define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ACCOUNT);
// define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ADM);
// define('IMA_CFG_LOGIN', IMA_LOGINTYPE_ADMAUTO);

// ADMINISTRATOR'S NAME AND PASSWORD

define('IMA_CFG_ADM_USER',  'admin');      // admin username
define('IMA_CFG_ADM_PASS',  '<password>'); // admin password

// LISTS
// Spread long lists on multiple pages.  Set number of maximum entries
// per page.  Changes take effect after login/logout.  If not defined,
// defaults to 65535.

// define('IMA_LIST_MAX_ENTRIES', 200);
?>

After finalization you should now be able to log in and set up your virtual mailboxes.

Rainloop

Start with the Non-Proxied Web Site template.

Change the following sections of the nginx conf file:

/etc/nginx/sites-available/<domain>.conf
...
	location ~ /data {
		deny			all;
	}
...
	location ~ \.php$ {
		fastcgi_pass		unix:/run/php-fpm/php-fpm.sock;
		fastcgi_index		index.php;
		include			fastcgi.conf;
		fastcgi_split_path_info	^(.+\.php)(.*)$;
		fastcgi_param		SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}
...

Download Rainloop

sudo cd /srv/http/<domain>
sudo wget https://www.rainloop.net/repository/webmail/rainloop-community-latest.zip

Extract the zip file

sudo unzip rainloop-community-latest.zip

Set the correct permissions on the files

sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;

Set the correct owner on the whole web root

sudo chown -R http:http /srv/http/<dir>

After you finalize the setup, you should now be able to access the Admini Panel at http://<domain>/?admin. The default login is admin:12345

From the Admin Panel, add your domain under Domains. Use the public-facing domain for the Name, and localhost for the IMAP and SMTP servers.

Under Login, set the Default Domain to localhost.

Optionally, you can enable contacts. You will need to manually set up a database and user, but that is the only configuration needed to do on your part.

For Security, you should enable all of the Security options. You should also change the Admin Panel Access Credentials.

Click on the power-off icon in the upper-right corner to finish with the Admin Panel.

Navigate to http://<domain> to log in and access your mail.

OnlyOffice Document Server

On Arch, OnlyOffice can be installed via an AUR package, with Docker, or with Snap.

AUR Package

The AUR package was broken at the time of this writing.

Snap

Snap did not work for me. However, this might have been due to the Snap's Redis service conflicting with that of the host's.

Docker

The Docker version requires PostreSQL, RabbitMQ, and Redis to be set up. Be sure to install and configure those first.

Once that is complete, set up the database and database user.

sudo -u postgres psql -c "CREATE DATABASE onlyoffice;"
sudo -u postgres psql -c "CREATE USER onlyoffice WITH password 'onlyoffice';"
sudo -u postgres psql -c "GRANT ALL privileges ON DATABASE onlyoffice TO onlyoffice;"

Create the following directories.

sudo mkdir /var/{lib,log}/onlyoffice

Issue one command to download and start the Docker instance.

sudo docker run -i -t -d -p [port]:80 --restart=always \
   -v /app/onlyoffice/DocumentServer/logs:/var/log/onlyoffice  \
   -v /app/onlyoffice/DocumentServer/lib:/var/lib/onlyoffice \
   -v /app/onlyoffice/DocumentServer/db:/srv/psql  onlyoffice/documentserver

Configure nginx to act as a proxy

/etc/nginx/sites-available/<domain>
upstream docservice {
	server <docker-ip>:8888;
}

map $http_host $this_host {
	""	$host;
	default	$http_host;
}

map $http_x_forwarded_proto $the_scheme {
	default	$http_x_forwarded_proto;
	""	$scheme;

}

map $http_x_forwarded_host $the_host {
	default	$http_x_forwarded_host;
	""	$this_host;
}

map $http_upgrade $proxy_connection {
	default	upgrade;
	""	close;
}

proxy_set_header	Upgrade $http_upgrade;
proxy_set_header	Connection $proxy_connection;
proxy_set_header	X-Forwarded-Host $the_host;
proxy_set_header	X-Forwarded-Proto $the_scheme;
proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;

server {
	listen		80;
	listen		[::]:80;
	server_name	<domain>;
	server_tokens	off;
	rewrite		^ https://$host$request_uri? permanent;
}

server {

	listen				443 ssl http2;
	listen				[::]:443 ssl http2;
	server_name			ods.bwt.com.de;
	server_tokens off;

	ssl_certificate			/etc/letsencrypt/live/<domain>/fullchain.pem;
	ssl_certificate_key		/etc/letsencrypt/live/<domain>/privkey.pem;
	ssl_trusted_certificate		/etc/letsencrypt/live/<domain>/chain.pem;

	add_header			Strict-Transport-Security max-age=31536000;
#	add_header			X-Frame-Options SAMEORIGIN;
	add_header			X-Content-Type-Options nosniff;

	access_log			/var/log/nginx/access.log main buffer=32k;
	error_log			/var/log/nginx/error.log error;
	limit_req			zone=gulag burst=200 nodelay;


	# ACME challenge
	location ^~ /.well-known {
		allow			all;
		alias			/var/lib/letsencrypt/$host/.well-known;
		default_type		"text/plain";
		try_files		$uri =404;
	}

	location / {
		proxy_pass		http://docservice;
		proxy_http_version	1.1;
	}
}

After finalizing, you should now be able to navigate to https://<domain> and see the OnlyOffice Document Server welcome page with a green checkmark indicating everything is running properly.