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.
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-enabledThe sites-available directory looks like
000-default.conf
«example.com».conf
default-ssl.confThe sites enabled directory looks like
000-default.conf -> ../sites-available/000-default.conf
«example.com»-le-ssl.conf
«example.com».confAnd 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 --apacheif 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>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 verboseBrowse 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..
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
mariadbYou 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_databaseCREATE 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;
exitOK, 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.
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/configserver {
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 nginxBrowse 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.phpThen 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.
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 httpsNot 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 -tThis 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 --nginxIf 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 -tTo 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 --http01Because 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;
exitapt-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
mariadbCREATE DATABASE wordpress DEFAULT CHARACTER SET
utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL ON wordpress.* TO 'wordpress_user'@'localhost'
IDENTIFIED BY '«password»';
FLUSH PRIVILEGES;
exitThe 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.phpReplace 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 nginxIt should now be possible to navigate to your wordpress domain in your web browser and finish the setup there:
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.
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 nginxIn 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
systemctl stop nginx
# we don’t want anyone browsing the blog while we are setting it up
# nor the wordpress update service running.
mariadbDROP DATABASE IF EXISTS wordpress;
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8;
GRANT ALL ON wordpress.* TO 'wordpress_user'@'localhost'
IDENTIFIED BY '«password»';
exitAt 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 $dbSHOW TABLES;
SELECT COUNT(*) FROM wp_posts;
SELECT * FROM wp_posts l LIMIT 20;
exitAdjust $table_prefix = 'wp_'; in wp_config.php if necessary.
systemctl start nginxInside 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 $dbSHOW 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 nginxYour blog should now work.
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/nginxThen 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.
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.
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.
reaction.la gpg key 154588427F2709CD9D7146B01C99BB982002C39F
This work is licensed under the Creative Commons Attribution 4.0 International License.