logo
vision scalability social networking revelation source

Web servers

abstract: Setting up web servers: Apache virtual hosting, LEMP stack (nginx, MariaDB, PHP), SSL/DNSSEC with certbot, WordPress installation and migration, and logging with awstats.

1 Apache

To bring up apache virtual hosting

Apache2 html files are at /var/www/<domain_name>/.

Apache’s virtual hosts are:
/etc/apache2/sites-available
/etc/apache2/sites-enabled

The apache2 directory looks like:

    apache2.conf
    conf-available
    conf-enabled
    envvars
    magic
    mods-available
    mods-enabled
    ports.conf
    sites-available
    sites-enabled

The sites-available directory looks like

    000-default.conf
    «example.com».conf
    default-ssl.conf

The sites enabled directory looks like

    000-default.conf -> ../sites-available/000-default.conf
    «example.com»-le-ssl.conf
    «example.com».conf

And the contents of «example.com».conf are (before the https thingly has worked its magic)

    <VirtualHost *:80>
        ServerName «example.com»
        ServerAlias www.«example.com»
        ServerAlias «foo.«example.com»»
        ServerAlias «bar.«example.com»»
        ServerAdmin «me@mysite»
        DocumentRoot /var/www/«example.com»

        <Directory /var/www/«example.com»>
            Options -Indexes +FollowSymLinks
            AllowOverride All
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/«example.com»-error.log
        CustomLog ${APACHE_LOG_DIR}/«example.com»-access.log combined
    RewriteEngine on
        RewriteCond %{HTTP_HOST} ^www\.«example.com»\.com [NC]
        RewriteRule ^(.*)$ https://«example.com»/$1 [L,R=301]
    </VirtualHost>

All the other files don’t matter. The conf file gets you to the named server. The contents of /var/www/«example.com» are the html files, the important one being index.html.

To get free, automatically installed and configured, ssl certificates and configuration install certbot, then

certbot register --register-unsafely-without-email --agree-tos
certbot --apache

if you have set up http virtual apache hosts for every name supported by your nameservers, and only those names, certbot automagically converts these from http virtual hosts to https virtual hosts and sets up redirect from http to https.

If you have an alias server such as www.«example.com» for «example.com», certbot will guess you also have the domain name www.«example.com» and get a certificate for that.

Thus, after certbot has worked its magic, your conf file looks like

    <VirtualHost *:80>
        ServerName «example.com»
        ServerAlias foo.«example.com»
        ServerAlias bar.«example.com»
        ServerAdmin me@mysite
        DocumentRoot /var/www/«example.com»

        <Directory /var/www/«example.com»>
            Options -Indexes +FollowSymLinks
            AllowOverride All
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/«example.com»-error.log
        CustomLog ${APACHE_LOG_DIR}/«example.com»-access.log combined
    RewriteEngine on
        RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
        RewriteRule ^(.*)$ https://«example.com»/$1 [L,R=301]
        RewriteCond %{SERVER_NAME} =«example.com» [OR]
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
    </VirtualHost>

2 Lemp stack on Debian

systemctl stop apache2
apt-get remove --purge apache2  #apache is routinely installed by default,
# and then nginix tries to respect its files and configuration,
# with confusing and disturbing results.
# Apache must die.  DIE DIE DIE.
apt-get upgrade
apt-get -qy update && apt-get -qy install ufw nginx mariadb-server  php php-cli php-xml php-mbstring php-mysql php-fpm
nginx -t
ufw status verbose

Browse to your server, and check that nginx web page is working. Your browser will probably give you an error page, merely because it defaults to https, and https is not yet working. Make sure you are testing http, not https. We will get https working shortly..

2.1 Mariadb and ufw

ufw default deny incoming && ufw default allow outgoing
ufw allow ssh &&  ufw allow 'Nginx Full' && ufw limit ssh/tcp
# edit /etc/default/ufw so that MANAGE_BUILTINS=yes
cat /etc/default/ufw | sed 's/^\#*[[:blank:]]*MANAGE_BUILTINS[[:blank:]]*=.*$/MANAGE_BUILTINS=yes/g' >tempufw
mv tempufw /etc/default/ufw
# "no" is bug compatibility with software long obsolete
ufw enable && ufw status verbose
# Status: active
# Logging: on (low)
# Default: deny (incoming), allow (outgoing), disabled (routed)
# New profiles: skip
# To                         Action      From
# --                         ------      ----
# 22/tcp (SSH)               ALLOW IN    Anywhere
# 80,443/tcp (Nginx Full)    ALLOW IN    Anywhere
# 22/tcp                     LIMIT IN    Anywhere
# 22/tcp (SSH (v6))          ALLOW IN    Anywhere (v6)
# 80,443/tcp (Nginx Full (v6)) ALLOW IN    Anywhere (v6)
# 22/tcp (v6)                LIMIT IN    Anywhere (v6)
mariadb_secure_installation
#empty root password
#Don't set a root password
#remove anonymous users
#disallow remote login
#drop test database
mariadb

You should now receive a message that you are in the mariadb console

CREATE DATABASE example_database;
GRANT ALL ON example_database.* TO  'example_user'@'localhost'
IDENTIFIED BY 'mypassword' WITH GRANT OPTION;
FLUSH PRIVILEGES;
exit
mariadb -u example_user --password=mypassword example_database
CREATE TABLE todo_list (      item_id INT
AUTO_INCREMENT,      content VARCHAR(255),
PRIMARY KEY(item_id)  );
INSERT INTO todo_list (content) VALUES
("My first important item");
INSERT INTO todo_list (content) VALUES
("My second important item");
SELECT * FROM todo_list;
exit

OK, MariaDB is working. We will use this trivial database and easily guessed example_user with the easily guessed password mypassword for more testing later. Delete him and his database when your site has your actual content on it.

2.2 domain names and PHP under nginx

Check again that the default nginx web page comes up when you browse to the server.

Create the directories /var/www/«subdomain.example.com» and /var/www/«example.com» and put some html files in them, substituting your actual domains for the example domains.

mkdir /var/www/«example.com» && nano /var/www/«example.com»/index.html
mkdir /var/www/«subdomain.example.com» && nano /var/www/«subdomain.example.com»/index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        </head>
    <body><h1>«example.com» index file</h1></body>
    </html>

Delete the default in /etc/nginx/sites-enabled, and create a file, which I arbitrarily name config that specifies how your domain names are to be handled, and how php is to be executed for each domain names.

This config file assumes your domain is called «example.com» and your service is called php-fpm.service. Create the following config file, substituting your actual domains for the example domains, and your actual php fpm service for the fpm service.

nginx -t
# find the name of your php fpm service
systemctl status php* | grep fpm.service
systemctl stop nginx
rm -v /etc/nginx/sites-enabled/*
nano /etc/nginx/sites-enabled/config
server {
    return 301 $scheme://«example.com»$request_uri;
    }
server {
    listen 80;
    listen [::]:80;
    index index.php index.html;
    server_name «subdomain.example.com»;
    root /var/www/«subdomain.example.com»;
    index index.php index.html;
    location / {
        try_files $uri $uri/ =404;
        }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    location = /favicon.ico {access_log off; }
    location = /robots.txt {access_log off; allow all; }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        }
    }
server {
    listen 80;
    listen [::]:80;
    index index.php index.html;
    server_name «example.com»;
    root /var/www/«example.com»;
    location / {
        try_files $uri $uri/ =404;
        }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    location = /favicon.ico {access_log off; }
    location = /robots.txt {access_log off; allow all; }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        }
    }
server {
    server_name *.«subdomain.example.com»;
    return 301 $scheme://«subdomain.example.com»$request_uri;
    }

The first server is the default if no domain is recognized, and redirects the request to an actual server, the next two servers are the actual domains served, and the last server redirects to the second domain name if the domain name looks a bit like the second domain name. Notice that this eliminates those pesky wwws.

The root tells it where to find the actual files.

The first location tells nginx that if a file name is not found, give a 404 rather than doing the disastrously clever stuff that it is apt to do, and the second location tells it that if a file name ends in .php, pass it to php-fpm.sock (you did substitute your actual php fpm service for php-fpm.sock, right?)

Now check that your configuration is OK with nginx -t, and restart nginx to read your configuration.

nginx -t
systemctl restart nginx

Browse to those domains, and check that the web pages come up, and that www gets redirected.

Now we will create some php files in those directories to check that php works.

echo "<?php phpinfo(); ?>"  |tee /var/www/«example.com»/info.php

Then take a look at info.php in a browser.

If that works, then create the file /var/www/«example.com»/index.php containing:

<?php
    $user = "example_user";
    $password = "mypassword";
    $database = "example_database";
    $table = "todo_list";
    try {
        $db = new PDO("mysql:host=localhost;dbname=$database", $user, $password);
        echo "<h2>TODO</h2><ol>";
        foreach($db->query("SELECT content FROM $table") as $row) {
            echo "<li>" . $row['content'] . "</li>";
            }
            echo "</ol>";
            }
    catch (PDOException $e) {
        print "Error!: " . $e->getMessage() . "<br/>";
        die();
        }
    ?>

Browse to http://«example.com» If that works, delete the info.php file as it reveals private information. You now have domain names being served by lemp. Your database now is accessible over the internet through PHP on those domain names.

2.3 SSL and DNSSEC

SSL encrypts communication between your server and the client, so that those in between cannot read it or change it.

It also somewhat protects against malicious people fooling the client into connecting to the wrong server. Unfortunately there are a thousand certificate authorities, and some of them are malicious or hostile, and if you have powerful enemies (and who cares about powerless enemies) they will cheerfully issue a certificate your enemy for your domain name. DNSSEC somewhat protects against this, since there is only one root of trust

If you are reading this document, you are self hosting, in which case your registrar is probably providing your nameservers, in which case it is easy for them to set up DNSSEC for you. You just have to click the correct button on their website. One click, and it is done. And now you only have to worry about two parties that might potentially defect on you, the DNSSEC and your registrar, instead of a thousand certificate authorities.

If, however, someone other than your registrar is managing your nameserver, if your DNS records live on a machine controlled by one entity, and your nameserver is controlled by a different entity, attempting to set up DNNSEC gets complicated, and if that someone is not you, considerably more complicated. In this case setting up DNSSEC is like setting up SSH, but when you are setting up SSH, you control both machines. When you attempt to setup DNSSEC you don’t. Don’t even try. If you do try, make very sure the nameserver is doing the right thing before you submit the DNSSEC public key you generated to the registrar and attempt to get the registrar to do the right thing.

OK, DNSSEC was easy. (Or you just gave up because far too hard.) Now on to SSL

Create the necessary DNS records, an A record pointing to your IP4 address, an AAAA record pointing to your IP6 address, a CAA record indicating who is the right issuer for your SSL certificate, so that not every certificate authority in the world is allowed to issue fake certificates for your enemies, and CNAME records for the www and git aliases.

The CAA record looks like:

@ CAA 0 issue "letsencrypt.org"

Go to whatsmydns and check if it looks right.

Certbot provides a very easy utility for installing ssl certificates, and if your domain name is already publicly pointing to your new host, and your new host is working as desired, without, however, ssl/https that is great.

To get free, automatically installed and configured, ssl certificates and configuration install certbot, then

# first make sure that your http only website is working as
# expected on your domain name and each subdomain.
# certbots many mysterious, confusing, and frequently
# changing behaviors expect a working environment.
certbot register --register-unsafely-without-email --agree-tos
certbot --nginx
# This also, by default, sets up automatic renewal,
# and reconfigures everything to redirect to https

Not so great if you are setting up a new server, and want the old server to keep on servicing people while you set up the new server, so here is the hard way, where you prove that you, personally, control the DNS records, but do not prove that the server that certbot is modifying is right now publicly connected as that domain name.

(Obviously on your network the domain name should map to the new server. Meanwhile, for the rest of the world, the domain name continues to map to the old server, until the new server works.)

certbot register --register-unsafely-without-email --agree-tos
certbot run -a manual  --preferred-challenges dns -i nginx \
    -d «example.com» -d «subdomain.example.com»
nginx -t

This does not set up automatic renewal. To get automatic renewal going, you will need to renew with the webroot challenge rather than the manual once DNS points to this server.

This, --preferred-challenges dns, also allows you to set up wildcard certificates, but it is a pain, and does not support automatic renewal. Automatic renewal requires of wildcards requires the cooperation of certbot and your dns server, and is different for every organization, so only the big boys can play.

But if you are doing this, not on your test server, but on your live server, the easy way, which will also setup automatic renewal and configure your webserver to be https only, is:

certbot --nginx

If instead you already have a certificate, because you copied over your /etc/letsencrypt directory

apt-get -qy install certbot python-certbot-nginx
certbot install  -i nginx
nginx -t

To renew certbot certificates, which has to be done every couple of months:
If you previously did the manual challenge, then certbot renew will likely fail (because no default non manual challenge exists). You need to set the renewal parameters for renewal to take place.

certbot renew --renew-by-default  --http01

Because certbot automatically renews using the previous defaults, you have to have previously used a process to obtain certificate suitable for automation, which mean you have to have given it the information
(--webroot --webroot-path /var/www/«example.com»)
about how to do an automatic renewal by actually obtaining a certificate that way.

To backup and restore letsencrypt, to move your certificates from one server to another, rsync -HAvaX «example.com»:/etc/letsencrypt /etc, as root on the computer which will receive the backup. The letsencrypt directory gets mangled by tar, scp and sftp.

Again, browse to your server. You should get redirected to https, and https should work.

Backup the directory tree /etc/letsencrypt/, or else you can get into situations where renewal is a problem. Only Linux to Linux backups work, and they do not exactly work – things go wrong. Certbot needs to fix its backup and restore process, which is broken. Apparently you should backup certain directories but not others. But backing up and restoring the whole tree works well enough for certbot install -i nginx

The certbot modified file for your ssl enabled domain should now look like

server {
    return 301 $scheme://«example.com»$request_uri;
    }
server {
    index index.php index.html;
    server_name «subdomain.example.com»;
    root /var/www/«subdomain.example.com»;
    index index.php;
    location / {
        try_files $uri $uri/ =404;
        }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    location = /favicon.ico {access_log off; }
    location = /robots.txt {access_log off; allow all; }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        }
    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/«example.com»/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/«example.com»/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    }
server {
    index index.html;
    server_name «example.com»;
    root /var/www/«example.com»;
    location / {
        try_files $uri $uri/ =404;
        }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    location = /favicon.ico {access_log off; }
    location = /robots.txt {access_log off; allow all; }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        }
    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/«example.com»/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/«example.com»/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    }
server {
    server_name *.«subdomain.example.com»;
    return 301 $scheme://«subdomain.example.com»$request_uri;
    }
server {
    server_name *.«example.com»;
    return 301 $scheme://«example.com»$request_uri;
    }
server {
    if ($host = «example.com») {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    listen [::]:80;
    server_name «example.com»;
    return 404; # managed by Certbot
    }
server {
    if ($host = «subdomain.example.com») {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    listen [::]:80;
    server_name «subdomain.example.com»;
    return 404; # managed by Certbot
    }

You may need to clean a few things up after certbot is done.

The important lines that certbot created in the file being ssl_certificate, the additional servers listening on port 80 which exist to redirect http to https servers listening on port 403, and that all redirects should be https instead of $scheme (fix them if they are not).

nginx starts as root, but runs as unprivileged user www-data, who needs to have read permissions to every relevant directory. If you want to give php write permissions to a directory, or restrict www-data and pgp’s read permissions to some directories and not others, you could do clever stuff with groups and users, giving creating users that php scripts act as, and making www-data a member of their group, but that is complicated and easy to get wrong.

A quick fix is to chown -R www-data:www-data the directories that your web server needs to write to, and only those directories, though I can hear security gurus gritting their teeth when I say this.

For all the directories that www-data merely needs to read:

find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;

Now you should delete the example user and the example database:

mariadb
REVOKE ALL PRIVILEGES, GRANT OPTION FROM
'example_user'@'localhost';
DROP USER 'example_user'@'localhost';
DROP DATABASE example_database;
exit

2.4 Wordpress on Lemp

apt-get -qy install php-curl php-gd php-intl php-mbstring php-soap php-xml php-xmlrpc zip php-zip
systemctl status php* | grep fpm.service
# restart the service indicated above
systemctl stop nginx
systemctl stop php-fpm.service
mariadb
CREATE DATABASE wordpress DEFAULT CHARACTER SET
utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL ON wordpress.* TO 'wordpress_user'@'localhost'
IDENTIFIED BY '«password»';
FLUSH PRIVILEGES;
exit

The lemp server block that will handle the wordpress domain needs to pass urls to index.php instead of returning a 404. (Handle your 404s and redirects issues with the Redirections Wordpress plugin, which is a whole lot easier, safer, and more convenient than editing redirects into your /etc/nginx/sites-enabled/* files.)

server {
    . . .
    location / {
        #try_files $uri $uri/ =404;
        try_files $uri $uri/ /index.php$is_args$args;
        }
    . . .
    }
nginx -t
mkdir temp
cd temp
curl -LO https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
cp -v wordpress/wp-config-sample.php wordpress/wp-config.php
cp -av wordpress/* /var/www/«subdomain.example.com»
chown -R www-data:www-data /var/www/«subdomain.example.com» && find /var/www -type d -exec chmod 755 {} \; && find /var/www -type f -exec chmod 644 {} \;
# so that wordpress can write to the directory
curl -s https://api.wordpress.org/secret-key/1.1/salt/
nano /var/www/«subdomain.example.com»/wp-config.php

Replace the defines that are there
define('LOGGED_IN_KEY', 'put your unique phrase here');
with the defines you just downloaded from wordpress.

and replace DB_NAME, DB_USER, DB_PASSWORD, and FS_METHOD

…
// ** Mariadb settings //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'wordpress_user');
/** MySQL database password */
define('DB_PASSWORD', '«password»');
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );
/** The Database Collate type. */
define( 'DB_COLLATE', 'utf8mb4_unicode_ci' );
…
systemctl start nginx

It should now be possible to navigate to your wordpress domain in your web browser and finish the setup there:

2.5 Exporting databases

Interacting directly with your database of the MariaDB command line is apt to lead to disaster.

Installing PhpMyAdmin has a little gotcha on Debian 9, which is covered in this tutorial, but I just do not use PhpMyAdmin even though it is easer and safer.

2.5.1 To export by command line

systemctl stop nginx
systemctl stop php-fpm.service
mdir temp && cd temp
fn=blogdb
db=wordpress
dbuser=wordpress_user
dbpass=«password»
mysqldump -u $dbuser --password=$dbpass $db > $fn.sql
head -n 30 $fn.sql
zip $fn.sql.zip $fn.sql
systemctl start php-fpm.service
systemctl start nginx

2.6 Moving a wordpress blog to new lemp server

In the course of this operation, you will probably be copying lots of files, and will repeatedly muck up permissions. For wordpress to be happy:

chown -R root:www-data /var/www
# Set all directories to 775 (gives group write permission)
find /var/www -type d -exec chmod 775 {} \;
# Set all files to 664 (gives group write permission)
find /var/www -type f -exec chmod 664 {} \;

Prerequisite: you have configured Wordpress on Lemp

Copy everything from the web server source directory of the previous wordpress installation to the web server of the new wordpress installation.

chown -R www-data:www-data /var/www/«subdomain.example.com»
find /var/www -type d -exec chmod 775 {} \;
find /var/www -type f -exec chmod 664 {} \;

Replace the defines for DB_NAME, DB_USER, and DB_PASSWORD in wp_config.php, as described in Wordpress on Lemp

2.6.1 To import database by command line

systemctl stop nginx
# we don’t want anyone browsing the blog while we are setting it up
# nor the wordpress update service running.
mariadb
DROP DATABASE IF EXISTS wordpress;
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8;
GRANT ALL ON wordpress.* TO 'wordpress_user'@'localhost'
IDENTIFIED BY '«password»';
exit

At this point, the database is still empty, so if you start nginx and browse to the blog, you will get the wordpress five minute install, as in Wordpress on Lemp. Don’t do that, or if you start nginx and do that to make sure everything is working, then start over by deleting and recreating the database as above.

Alternatively, if you want to merge this content into an blog that has already been setup, perhaps an almost empty blog, you remove all the drop table commands and create table commands from the sql, and replace all the INSERT INTO statements with INSERT IGNORE INTO

Now we will populate the database.

tar -xvf wordpress.sql.zst
fn=wordpress
db=wordpress
dbuser=wordpress_user
dbpass=«password»
mariadb -u $dbuser --password=$dbpass $db < $fn.sql
mariadb -u $dbuser --password=$dbpass $db
SHOW TABLES;
SELECT COUNT(*) FROM wp_posts;
SELECT * FROM wp_posts l LIMIT 20;
exit

Adjust $table_prefix = 'wp_'; in wp_config.php if necessary.

systemctl start nginx

Inside the sql file may be references to the old directories, (search for 'recently_edited'), and to the old user who had the privilege to create views (search for DEFINER=) Replace them with the new directories and new database user, in this example wordpress_user.

Edit the siteurl,admin_email and new_admin_email fields of the blog database to the domain and new admin email.

mariadb -u $dbuser --password=$dbpass $db < $db.sql
mariadb -u $dbuser --password=$dbpass $db
SHOW TABLES;
SELECT COUNT(*) FROM wp_comments;
SELECT * FROM wp_comments l LIMIT 10;

Adjust $table_prefix = 'wp_'; in wp_config.php if necessary.

systemctl start nginx

Your blog should now work.

3 Logging and awstats.

3.1 Logging

First create, in the standard and expected location, a place for nginx to log stuff.

mkdir /var/log/nginx
chown -R www-data:www-data /var/log/nginx

Then edit the virtual servers to be logged, which are in the directory /etc/nginx/sites-enabled and in this example in the file /etc/nginx/sites-enabled/config

server {
    server_name «example.com»;
    root /var/www/«example.com»;
    …
    access_log  /var/log/nginx/«example.com».access.log;
    error_log  /var/log/nginx/«example.com».error.log;
    …
    }

The default log file format logs the ips, which in a server located in the cloud might be a problem. People who do not have your best interests at heart might get them.

So you might want a custom format that does not log the remote address. On the other hand, Awstats is not going to be happy with that format. A compromise is to create a cron job that cuts the logs daily, a cron job that runs Awstats, and a cron job that then deletes the cut log when Awstats is done with it.

There is no point to leaving a gigantic pile of data, that could hang you and your friends, sitting around wasting space.

4 installing a Wordpress blog on a new domain

Assuming you have a backup of the files and the database.

Create a freshly installed empty blog on the target site using one of the many easy Wordpress setups.

Copy over all the old files except wp_config.php

Edit the wp_config file so that the table_prefix agrees with the original blogs wp_config.php table_prefix.

Delete the new blog tables with new blog’s table prefix from the new blog’s database, and upload the old blog’s tables from the new blog’s database

Upload the sql file to the new blog database.

Should now just work.

5 Wordpress from the command line

Often you need to manipulate wordpress because it is crashing or locking, but you cannot manipulate it through the web interface because it is crashing or locking.

a common crisis is that wordpress is down and you need to use wp to disable some suspect plugins.

su -s /bin/bash www-data  #from root, become the wordpress user
# now we can issue wordpress commands from the command line.
wp plugin list --status=active
wp plugin deactivate sqlite-object-cache
wp plugin deactivate wp-super-cache
wp plugin list --status=active
exit #return to being root.

The AIs say use redis for wordpress caching, but any caching system is going to thrash when llms are download the blog from beginning to end.

Creative Commons License reaction.la gpg key 154588427F2709CD9D7146B01C99BB982002C39F
This work is licensed under the Creative Commons Attribution 4.0 International License.