Sysadmin

Setting up and managing a VPS (Virtual Private Server)

This guide will use CentOS 7 as the Linux distribution. If you use any other distribution, you should adjust the command lines shown in this guide accordingly.

Choosing a control panel

If you plan on hosting an Apache web server and a mail server on your VPS, you may consider installing a control panel. A control panel is a web-based interface allowing you to manage your web domains, email addresses, backups, etc.

Some common control panels are (paid) cPanel, Plesk, DirectAdmin and (free) Webmin/Virtualmin.

Personally I tried two of them (DirectAdmin and Virtualmin), before eventually deciding that I'm better off without a control panel. The main reasons I decided not to use any and ultimately turned to strongly dislike them:

I eventually decided to manage my server using the good old Linux command line.

Update installed OS and packages

The command below allows you to update the latest kernel patches and update installed applications. As these updates may come with security patches, this is the first command you should execute after first login.

# yum update && yum upgrade

If you still decided to go for a control panel anyway and if it came already pre-installed by your VPS provider, update the control panel to its latest version by following the procedure in their documentation.

Enable SELinux (Security-Enhanced Linux)

Check if SELinux is enabled and the mode it runs (enforcing/permissive):

# sestatus
# cat /etc/selinux/config
# getenforce

In order to enable SELinux (if it is currently disabled), you have to:

Change the hostname

A default hostname for your server is given by your VPS provider. Mine looked like vps-1234-5678.myvpsprovider.cloud. To view your current hostname, run the following command:

# hostnamectl

It is recommended to change the default hostname by something more human-readable: if you plan on running other servers later on, you'll want to easily identify which server you are working on in the command line to avoid any mistakes; also, some programs may write the server hostname in log files.

# hostnamectl set-hostname --static yourhostname

Note: I like the idea of aligning the hostname with the domain name for which there is an rDNS entry (which may then be considered as being the server's main domain name). This may be the domain name provided by your VPS (I could change mine from something like vps-1234-5678.myvpsprovider.cloud to a more readable name such as myservername.myvpsprovider.cloud through my VPS provider's interface), or this could be your domain name for which you have set up rDNS for this server.

Setup time and timezone

You can check if your time and timezone are correctly set by running the following command:

# timedatectl

It is recommended to run your servers in the UTC timezone, as well as storing UTC dates and times in databases. It will be much easier to write applications that have to display and manipulate dates for users coming from various timezones.

# timedatectl set-timezone UTC

You may not want to change the timezone if you have to run legacy applications which rely on the server to be set in the local timezone.

Setup the locale

Use localectl and locale to print the default system locale:

# localectl
# locale

My default locale is set to en_US.utf8. However, I want to prevent programs formatting dates in the American format MM/DD/YYYY.

# date --date=2019-03-04 +%x #prints: 03/04/19

Therefore, I'll look for other locales available:

# localectl list-locales | grep en
# localectl set-locale LANG=en_GB.utf8
# date --date=2019-03-04 +%x #prints: 04/03/19

Synchronize time with NTP

Make sure NTP is installed and enabled by running the following command:

# systemctl status ntpd.service

If this is not the case, run the following commands:

# yum install ntp
# systemctl start ntpd.service
# systemctl enable ntpd.service
# systemctl status ntpd.service

Add a user

It is safer to login and execute commands with a user that is not root. Execute the following commands to add a user and set its password:

# adduser username
# passwd username

Adding the user to the special wheel group allows the user to execute commands with root privileges using the sudo program.

# gpasswd --add username wheel

To launch a new shell with the new user:

# su - username

Even though the created user has access to root privileges by simply executing commands through the sudo program, at least now you have to be explicit when running commands for which you need elevated privileges.

To switch to the root user:

$ sudo -i

Disable SSH login for root

By disabling SSH login for the root user, an attacker will have to guess the user name in addition to the password, which may improve security.

# vim /etc/ssh/sshd_config

Find the line containing PermitRootLogin and change to no. Add the line AllowUsers username.

# systemctl restart sshd.service

Enforce SSH public key authentication

To enhance security of authentication, you can enforce SSH login to use public key authentication. You will need to generate a private key and a public key pair. Only your local machine will know and hold the private key, and the server will get a copy of the public key. Only a user in possession of a private key will be able to authenticate successfully to the server. You can use your private and public key pair for authentication with multiple servers.

How does it work?

When a client attempts to connect to a server with public key authentication, the client first needs to know if the server he is trying to connect to is truly the server it claims to be. This is known as the Host Key Verification. In order to achieve this verification, the server has to generate a public and private key pair as well. This pair of keys are generated when the SSH server is initialized.

  1. The SSH server gives the public key to the SSH client;
  2. the SSH client checks if the public key of the server he is trying to connect to is stored in the known_hosts file;
  3. if the entry does not exist, the SSH client may add the server's public key into known_hosts file;
  4. if the entry exists, the SSH client will use the server's public key to encrypt a message and expect the server to decrypt it with his private key. Only the private key can decrypt the message; so if the server successfully sent back the decrypted message, the client knows that the server is who he claims to be.

This mechanism assures that once a client connected to an SSH server the first time, every subsequent connection to that server's IP/hostname with SSH resolves to the same SSH server the client connected to the first time. If the server is replaced, the server's public and private key pair will change as well, and that will result in a failed Host Key Verification: the new public key is not listed in the client's known_hosts file.

After the server was verified by the client, the server must know if the user is who he claims to be. This is known as the Key-based Authentication

  1. The SSH client gives the public key to the SSH server;
  2. the SSH server checks if the public key of the client attempting to connect is stored in the /home/USERNAME/.ssh/authorized_keys file (where USERNAME is the remote username specified during login);
  3. if the entry exists, the SSH server will use the client's public key to encrypt a message and expect the client to decrypt it with his private key. Only the private key can decrypt the message; so if the client successfully sent back the decrypted message, the server knows that the client is who he claims to be.

That was almost a copy paste... the Host Key Verification and the Key-based Authentication work basically the same way.

Generate the local key pair

My local machine is running on Windows, so the commands that need to be executed locally may be different for you if you're running another OS.

C:\Users\Myself> ssh-keygen

ssh-keygen will generate your private (id_rsa) and public (id_rsa.pub) key pair in the folder you specified.

Note: you will be asked for a passphrase. This is optional; the passphrase protects the private key in case your local machine is compromised (e.g. stolen laptop). For most use cases, a passphrase is recommended.

Give the server a copy of the generated public key

On the server, switch to the user for whom you want to give SSH access through public key authentication, and let the server authorize your public key by adding it into the user's ~/.ssh/authorized_keys file.

# su - username
$ mkdir .ssh
$ chmod 700 .ssh
$ vim .ssh/authorized_keys #paste the content of your public key (id_rsa.pub) in this file
$ chmod 600 .ssh/authorized_keys

Configure the SSH server

$ sudo -i
# vim /etc/ssh/sshd_config

You can require both public key and password authentication by adding the AuthenticationMethods option (the option may be called RequiredAuthentications2 according to your distro).

AuthenticationMethods publickey,password

Set the value of the following configuration options to no: PermitRootLogin, ChallengeResponseAuthentication, UsePAM.
Set the value of the following options to yes: PubkeyAuthentication, PasswordAuthentication.

Restart the SSH server.

# systemctl restart sshd.service

Convert the private key to PuTTY's format .ppk

I am running PuTTY as the SSH client on Windows in order to connect to the remote SSH server. However, PuTTY needs private keys to be in its own format .ppk. The generated rsa key needs to be converted from the OpenSSH format to the PuTTY format (ppk). Open the PuTTYgen utility, click on the Conversions/Import key menu, and convert the OpenSSH key (id_rsa). Once the private .ppk key has been generated, open the PuTTY client and navigate through the Connection/SSH/Auth menus. You will be able to specify your private key in the field "Private key file for authentication".

Note: the local known_hosts file that was mentioned earlier is stored by PuTTY under a Windows registry key.

I am using WinSCP for file transfers with SFTP (SSH File Transfer Protocol). SFTP uses SSH, so for this reason I had to specify the private key (in the PuTTY .ppk format) in WinSCP for authentication as well.

When connecting to the server, you will from now on be asked to enter the passphrase of your private key, followed by the password of your server's user.

Enable the firewall

Check if the firewall is enabled by running the following command:

# systemctl status firewalld.service

If this is not the case, run the commands below to enable the firewall.

# systemctl enable firewalld.service
# firewall-cmd --state

Change the SSH port

Changing the default SSH port number will reduce the number of automated bots from brute-forcing your server.

Tell SELinux that you want to change the port number for SSH (change 1234 below by your desired port number).

# semanage port -a -t ssh_port_t -p tcp 1234

Change the port number in the sshd_config configuration file.

# vim /etc/ssh/sshd_config

Lastly, open the new port in the firewall.

# firewall-cmd --zone=public --permanent --add-port=1234/tcp
# firewall-cmd --zone=public --permanent --remove-service=ssh
# firewall-cmd --reload
# systemctl restart sshd.service

Install Apache or other web server

The following commands install Apache. If you plan on running another web server, skip this part and install the required packages for your web server.

# yum install httpd
# systemctl start httpd.service
# systemctl enable httpd.service
# systemctl status httpd.service

To find Apache's version and configuration file, execute the command below. The value of HTTPD_ROOT is the path to the directory holding the configuration files. SERVER_CONFIG_FILE is the path to the main configuration file (relative to the aforementioned directory).

# httpd -V

My main config file is located at /etc/httpd/conf/httpd.conf. In order to find the directory from which web files will be served by Apache, find the lines mentioning DocumentRoot and Directory. I delete the /var/www/cgi-bin directory (cgi-bin is hardly ever used these days) as well as the associated Directory directives in from the config file.

# rmdir /var/www/cgi-bin

Install PHP or other server language

# yum install centos-release-scl
# yum install rh-php72
# scl enable rh-php72 bash
# php --version

SCL allows you to run php commands and run multiple versions of PHP on the same web server. Make SCL persist after a logout/reboot by prepending source scl_source enable rh-php72 to the file ~/.bash_profile.

# echo "$(echo "source scl_source enable rh-php72"; cat ~/.bash_profile)" > ~/.bash_profile

Hook Apache with PHP:

# yum install rh-php72-php-fpm
# systemctl enable rh-php72-php-fpm.service
# systemctl start rh-php72-php-fpm.service
# cat > /etc/httpd/conf.d/php-fpm.conf << EOF
AddType text/html .php 
DirectoryIndex index.php
<FilesMatch \.php$>
      SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
EOF
# systemctl restart httpd.service

Install PDO and MariaDB/MySQL

# yum list rh-php72*
# yum install rh-php72-php-mysqlnd
# yum install rh-php72-php-pdo
# systemctl restart rh-php72-php-fpm.service

Improve PHP performance with OPcache.

# yum install rh-php72-php-opcache
# mkdir -p /etc/php.d/
# cat > /etc/php.d/opcache.ini << EOF
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=7963
opcache.validate_timestamps=0
opcache.revalidate_freq=0
EOF
# systemctl restart httpd.service
# systemctl restart rh-php72-php-fpm.service

Note: to find the adequate number for opcache.max_accelerated_files, count the approximate number of php scripts of your website with the command below, and get the best value from the list shown here: http://php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files

# find . -type f -print | grep -E "php|phtml" | wc -l

You can later check that OPcache is working by temporarily creating a php file that prints phpinfo(). For our website to work, we still have to configure the firewall and a virtual host.

# echo "<?= phpinfo() ?>" > phpinfo.php

Open a service in the firewall

After installing the web server, you should still not be able to access it using your server's IP. This is because the firewall has blocked the ports. Run the following commands to allow access to the web server:

# firewall-cmd --zone=public --permanent --add-service=http
# firewall-cmd --zone=public --permanent --add-service=https
# firewall-cmd --reload

You should now be able to access your web server by its IP and view a welcome page in your browser.

You can list all the services for which you can add a rule in the firewall.

# firewall-cmd --get-services

If you have to open a port for an application that isn't listed in the services' list, you can still add a rule for this application by port number. For instance, if you want to open access to an application listening on the port number 8080:

# firewall-cmd --zone=public --permanent --add-port=8080/tcp
# firewall-cmd --reload

To list all the existing rules that have been added:

# firewall-cmd --list-all

View open ports

Here are some utilities to view open ports and connections.

# yum install nmap
# nmap 127.0.01
# netstat -tulpn
# yum install lsof
# lsof -i -n -P | grep LISTEN

Update the database

In CentOS 7, the default installed database is MariaDB 5.5. Check if the database is installed and display its version:

# systemctl status mariadb.service
# mysql -V

In order to update MariaDB from 5.5 to 10.x, you must add the MariaDB repository (at the time of writing this article, the latest stable version is 10.3).

# vim /etc/yum.repos.d/MariaDB10.repo

And paste the content below in the file.

# MariaDB 10.3 CentOS repository list
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.3/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

Remove the old version and install MariaDB 10.x:

# systemctl stop mariadb.service
# yum remove mariadb*
# yum clean all
# yum install MariaDB-server MariaDB-client
# systemctl start mariadb.service
# systemctl enable mariadb.service
# mysql -V

Run these two commands to upgrade system tables and secure the installation:

# mysql_upgrade
# mysql_secure_installation

Install your websites

Upload your website's files

Create a new folder under /var/www for your website and upload its files with an SFTP client (such as WinSCP for Windows) using your SSH user name and password.

If you do not have a website yet but still wish to test the web server, add a simple file in the newly created directory.

# cd /var/www
# mkdir mysitename
# cd mysitename
# echo "welcome to my website" > index.html

Change the file permissions

For Apache (or other web server) to be able to read your website's files, you have to:

# chown -R username /var/www
# chgrp -R apache /var/www
# find /var/www -type d -exec chmod 750 {} \;
# find /var/www -type f -exec chmod 640 {} \;
# find /var/www -type d -exec chmod g+s {} \;

The first command above assumes Apache's group is apache. If you are not sure, you can find the user running the httpd process, and then print the groups to which this user belongs to.

# ps -eo comm,user | grep httpd

In my case, the user is apache. Print the group names of the apache user:

# groups apache

If you need files or directories to be writable, you have to add the write permission for the Apache group, and change the SELinux context. Files in the web directory have by default the SELinux label httpd_sys_content_t, which allows Apache only to read. You can view the labels with the command ls -Z. You have to change the label to httpd_sys_rw_content_t if you need files and directories to be writable.

# find /var/www/mysitename/logs -type d -exec chmod g+w {} \;
# semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/mysitename/logs(/.*)?"
# restorecon -vR /var/www/mysitename/logs

Add a Virtual Host file

Apache will load all configuration files that end with .conf from the /etc/httpd/conf.d/ directory. You can create a new configuration file for each Virtual Host in that directory.

To easily find the Virtual Host configuration file for a website, you can name the file to the domain name it relates to. Create a new configuration file, such as example.com.conf and paste the following content:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/example/public

    <Directory /var/www/example/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog /var/log/httpd/example-error.log
    CustomLog /var/log/httpd/example-access.log combined
</VirtualHost>

Test the configuration file syntax with:

# apachectl configtest

Then restart the web server:

# systemctl restart httpd.service

Point the domain's DNS records to your server

Point the DNS 'A' and 'AAAA' records of your domain name to your server's IP. The DNS 'A' record must contain the IP v4 address of your server while the 'AAAA' record must contain the IP v6 address. The DNS records may be changed through the interface of your domain name's provider.

Here are some useful links that allow to lookup DNS records:

View your website without a domain name

If you did not purchase a domain name yet, you may temporarily add a ServerAlias in the Virtual Host configuration file to match your server's IP.

ServerAlias 203.0.113.0

Install free LetsEncrypt SSL certificates for Apache

While SSH allows to establish a secure connection between two computers, SSL allows to establish a secure connection between a browser and a server. They both use a symmetric key to encrypt the communication between the two machines, and are equally secure.

Install mod_ssl module for Apache. Apache will now listen on port 443.

# yum install mod_ssl openssl

Install Certbot: Let’s Encrypt client used to obtain, install and renew SSL certificates. The certbot package is available from the EPEL repository. Follow the latest instructions on certbot's documentation https://certbot.eff.org/all-instructions.

# yum install epel-release
# yum install certbot python2-certbot-apache
# certbot --apache

You can test the configuration via this link: https://www.ssllabs.com/ssltest/analyze.html?d=YOUR_DOMAIN_NAME.COM

Add a cron job to automatically renew the certificate.

# crontab -e

Add the following line in the opened file:

0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew

Redirect www to non-www and http to https

Redirect the www version of your website to the non-www. E.g. http://www.example.com -> http://example.com.
Redirect http requests to https. E.g. http://example.com -> https://example.com.

# vim /etc/httpd/conf.d/example.com.conf

Add the following in the VirtualHost listening on port 80:

RewriteEngine on
RewriteCond %{SERVER_NAME} =example.com
RewriteRule ^ https://example.com%{REQUEST_URI} [END,NE,R=permanent]
RewriteCond %{SERVER_NAME} =www.example.com
RewriteRule ^ https://example.com%{REQUEST_URI} [END,NE,R=permanent]

And the following for port 443:

RewriteEngine on
RewriteCond %{SERVER_NAME} =www.afreshcloud.com
RewriteRule ^ https://afreshcloud.com%{REQUEST_URI} [END,NE,R=permanent]

Optimize web performance

Configure browser caching

Add a header in server responses instructing browsers how to cache your files. Read more about browser caching. Add the following lines into <VirtualHost *:443>.

<IfModule mod_headers.c>
    <FilesMatch "\.(css|js)$">
        Header append Cache-Control "max-age=43200"
    </FilesMatch>
    <FilesMatch "\.(webp|ico|jpg|jpeg|png|gif|svg)$">
        Header set Cache-Control "max-age=43200"
    </FilesMatch>
</IfModule>

Convert images to the .webp format

You may convert images to the .webp format which significantly reduces image size. This format is developed by Google and open source. I used Android Studio for converting my images, but there are other tools available.

Safari doesn't support the .webp format, so insert images into HTML as such:

<picture>
    <source type="image/webp" srcset="image.webp">
    <source type="image/jpeg" srcset="image.jpg">
    <img src="image.jpg" alt="My Image">
</picture>

For CSS background images, I added the JS snippet below

const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari) {
    document.documentElement.classList.add('safari');
}

and added a rule in the CSS if the safari class is present:

.cover {
    background: url(../images/a-fresh-cloud.webp) no-repeat center center;
}
.safari .cover {
    background-image: url(../images/a-fresh-cloud.jpg);
}

Further performance improvements

You can further improve your website's performance by using Google tools such as https://developers.google.com/speed/pagespeed/insights and Chrome's Developer tools, Audits tab. Improvements may also include migrating to HTTP/2; however I decided not to migrate for now, as that would require updating Apache and possibly tweak some modules, and I want to keep Apache updates under the control of the default yum repos.

Setup mail

To avoid having to deal with managing a mail server, handling incoming spam, ensuring security, and so on, I decided to use a third-party email hosting provider. Examples of known email hosting providers are G Suite (Google) and Office 365 (Microsoft). I wrote another article about how mail servers work, while I was still considering hosting my own mail server.

For sending out transactional emails (email triggered by a user action on a website or mobile app, like an account creation email), I decided to go for an SMTP relay service in order to ensure a high email deliverability rate. An example of a relay provider is SparkPost.

Backup the databases

Write scripts performing regular backups of your database. I wrote a separate post about mariabackup including bash scripts performing full and incremental backups at scheduled intervals.

Set up failover and DB replication

Get high-availability by setting up a failover server and DB replication.

Remove unnecessary services

To view the list of enabled and running services:

# systemctl list-unit-files | grep enabled
# systemctl list-units --type=service --state=running

In my case Postfix service is enabled and running by default. As I do not plan to host a mail server on my VPS, I disable and remove it.

# systemctl stop postfix.service
# yum remove postfix

Useful Linux commands to know

Monitoring disk, memory and CPU usage

Show the amount of disk space used and available on Linux file systems.

# df -h

Shows the amount of disk space used by the specified files.

# du -sh /var/www/yourwebsite/

Check memory usage (RAM + swap memory). Make sure that your server doesn't rely too much on swap memory.

# free -h
# free -h -s 3 -c 5
# vmstat -SM 1

Check CPU usage.

# top

You can sort, filter and perform other actions on the interactive list by pressing keys. Below are a few of them (case matters):

The ps command allows you to have a list of processes as well, but it will print a snapshot (not an interactive interface as provided by top). It is useful to extract information with shell pipelines as shown below. Common used options are:

# ps -eF | grep httpd
# ps -eo comm,user | grep httpd

View general system information

Print the Linux kernel version and the Linux distribution version.

# uname -mrs
# cat /etc/*-release

Print the name of the user's default shell program.

# echo $SHELL

Print the list of logged users and see what each user is doing.

# w

Print server's uptime

# uptime
# last reboot

Command line hotkeys

ctrl-a set the cursor at the beginning of the line
ctrl-e set the cursor at the end of the line
ctrl-u clear the text before the cursor (so it will clear the line if cursor is at the end of the line)
ctrl-w remove last word

To see all the available hotkeys:

# bind -p

When the user is not root and I want to execute commands that need elevated privileges, I often forget to prefix the command by sudo. In order to repeat the previous command with sudo:

# sudo !!

Test the internet speed of your VPS

# yum install wget
# wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
# chmod +x speedtest-cli
# ./speedtest-cli

You may also test the internet speed from another location. Find the an id number in the following file: https://www.speedtest.net/speedtest-servers-static.php, then execute the command:

# ./speedtest-cli --server the_id

Change the vim default color scheme

# echo "colorscheme desert" >> ~/.vimrc
# echo "syntax on" >> ~/.vimrc

To list the colors that are available:

# ls /usr/share/vim/ | grep vim*
# ls /usr/share/vim/vimYOUR_VIM_VERSION/colors/

Note: if you add the color scheme for a user in the wheel group, you'll have to change the color scheme for the root user as well if you want it to apply when executing commands with sudo.

More about SELinux

SElinux and labels

SELinux adds labels to all files, processes, ports, etc. SELinux has a set of rules that control which processes can access which labels. For example: a process running with label httpd_t can have read access to a file labeled httpd_config_t.

To see the labels of a file or other, you can use the -Z argument:

# ls -dZ /etc/httpd/
system_u:object_r:httpd_config_t:s0 /etc/httpd/

Change SELinux attributes

You can change SELinux attributes with tools such as:

SELinux's rules consist of booleans. To list all the booleans:

# getsebool -a

Diagnose and fix SELinux issues

In order to collect logs of SELinux events (such as access violation), install the following packages:

# yum install setroubleshoot setroubleshoot-server
# service auditd restart

View SELinux issues in the logs:

# journalctl -r -b -0

-b -0: show logs since the last reboot
-r: newest entries are displayed first

To disable SELinux temporarily (e.g. to check whether an error is due to SELinux or not):

# setenforce 0

SELinux example issues

Example 1: changing the default SSH port without telling SELinux

If you try to change the default SSH port without telling SELinux about it, you won't be able to restart the SSH service.

# vim /etc/ssh/sshd_config # change the default port to 2000
# systemctl restart sshd.service
Job for sshd.service failed because the control process exited with error code. See "systemctl status sshd.service" and "journalctl -xe" for details.
# journalctl -r -b -0

The logs are very clear and helpful as they give the exact command to run in order to allow SSH to listen on our custom port:


    SELinux is preventing /usr/sbin/sshd from name_bind access on the tcp_socket port 2000.

    *****  Plugin bind_ports (92.2 confidence) suggests   ************************

    If you want to allow /usr/sbin/sshd to bind to network port 2000
    Then you need to modify the port type.
    Do
    # semanage port -a -t PORT_TYPE -p tcp 2000
    where PORT_TYPE is one of the following: ssh_port_t, vnc_port_t, xserver_port_t.
        
# semanage port -a -t ssh_port_t -p tcp 2000

Example 2: incorrectly labeled file

SELinux expects the website's files (in the directory /var/www) to have the label httpd_sys_content_t.

# ls -Z index.php
-rw-r--r--. username apache unconfined_u:object_r:httpd_sys_content_t:s0 index.php

The current label for all our web files is httpd_sys_content_t. We can execute matchpathcon to view the default label that SELinux associates for that file:

# matchpathcon index.php
index.php       system_u:object_r:httpd_sys_content_t:s0

We can see that our web files are currently labeled by SELinux's default label for web files. Files labeled with httpd_sys_content_t can be read by Apache's process (labeled httpd_t).

# install setools-console
# sesearch -A -s httpd_t -t httpd_sys_content_t -c file -p read
allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock map open } ;

We can see the rule stating that the Apache process labeled httpd_t is allowed to read files labeled httpd_sys_content_t.

Now let's try to search for a label that the Apache process is not allowed to read.

# seinfo -xafile_type | sort | more

Let's check if httpd_t is allowed to read, for instance, files labeled shadow_t.

sesearch -A -s httpd_t -t shadow_t -c file -p read

No rules are displayed so it is not allowed. We change our web file's label to shadow_t:

chcon -t shadow_t index.php

The website doesn't work anymore. Let's have a look at the logs:

# journalctl -r -b -0

    SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/mywebsite/public/index.php.

    *****  Plugin restorecon (99.5 confidence) suggests   ************************

    If you want to fix the label.
    /var/www/mywebsite/public/index.php default label should be httpd_sys_content_t.
    Then you can run restorecon. The access attempt may have been stopped due to insufficient permissions to access a parent directory in which case try to change the following command accordingly.
    Do
    # /sbin/restorecon -v /var/www/mywebsite/public/index.php
        
# restorecon -v index.php

or to restore recursively on a directory:

# restorecon -vR /var/www/mywebsite/

Example 3: adding a custom root directory for web files

By default, SELinux expects all web files to be located in /var/www. Let's try to add a new website into a user's directory.

# mkdir -p /websites/mysitename
# cd /websites
# echo "welcome to my website" > mysitename/index.html
# chown -R username /websites
# chgrp -R apache /websites
# find /websites -type d -exec chmod 750 {} \;
# find /websites -type f -exec chmod 640 {} \;
# find /websites -type d -exec chmod g+s {} \;

Add a VirtualHost for the new website (matching the server's IP for example).

Test the configuration file syntax with:

# apachectl configtest

Then restart the web server:

# systemctl restart httpd.service

When trying to access the website and then running:

# journalctl -r -b -0

We get the following logs:


    SELinux is preventing /usr/sbin/httpd from read access on the file index.html.

    *****  Plugin catchall_labels (83.8 confidence) suggests   *******************

    If you want to allow httpd to have read access on the index.html file
    Then you need to change the label on index.html
    Do
    # semanage fcontext -a -t FILE_TYPE 'index.html'
    where FILE_TYPE is one of the following: NetworkManager_exec_t, NetworkManager_tmp_t, abrt_dump_oops_exec_t,
    Then execute:
    restorecon -v 'index.html'
        

We have to give the /websites directory the right context for containing web files, and then apply the context change to the files beneath it:

# semanage fcontext -a -t httpd_sys_content_t "/websites(/.*)?"
# restorecon -vR /websites

Comments

Comments including links will not be approved.