How Can We Help?

LAMP Server Migration

You are here:
< All Topics

The following notes document the migration of a virtual Linux machine running LAMP – Linux Ubuntu, Apache webserver, MySql/MariaDB & PHP, from an existing machine to a new machine and the procedural steps involved.


Both old and new machines also run ssh, nfs-server shares, sslh port multiplexer, nextcloud, checkmk, wordpress, phpmyadmin, together with Linux shell script crontab-run backup routines to backup data to an external cloud provider.


The scripts and crontab entries had to be migrated, installed and reconfigured on the new server.


This is an overview of the steps involved. For more detailed discussion of the specific actions, for example covering mysql, apache, sslh multiplexer, SSL certificates, see the appropriate relevant subject sections of this IT Knowledge Base.



Initial Preparation


First task is to create the new virtual server with Linux Ubuntu OS. This has a new localhost name and new unique publicly accessible IP address.


After the migration, the old original server machine is then switched off and deleted from the virtual server provider environment. The new machine name is changed to the old machine name, taking the place of the old machine online.


After installing the basic operating system Linux Ubuntu version 20.04 LTS, we migrate existing /usr/local/bin scripts from the old to the new machine, together with any binaries and other files contained in this directory.


Later we will configure crontab on the new machine for backups and mysql nextcloud nightly database scan and updates for newly added data files (and deleted/modified data files).


We give the new machine the temporary localhost name of “gemininew”.


When the migration is complete we will change the name to “gemini”, taking the place of the old machine.


The /etc/hosts file entries on the server and on all connecting hosts will then need to be modified so that the entry for gemini points to the NEW IP address and no longer to the old one.


In the meantime, for ease of logging in, we have made an entry on the old gemini server /etc/hosts and on our connecting hosts as follows:


gemininew  <the new IP address for gemini>


while keeping the old gemini IP entry intact for now. Later on towards the end of the migration this old entry will be deleted from the file.




Modify DNS Record


We need to modify the DNS record for the domain for the new IP address of the new machine to replace the old one.


This is done on the DNS server of our virtual server provider.



Install Apache


Install apache2 on the new machine and define the virtual hosts that are required.




Next, for apache


To allow .htaccess files, we need to set the AllowOverride directive within a Directory block pointing to our document root. Add the following block of text inside the VirtualHost block in your configuration file, making sure to use the correct web root directory.



In my case it is:


root@gemininew:/etc/apache2/sites-available# nano 000-default.conf

add this under

<VirtualHost *:80>


<Directory /var/www/wordpress/>
AllowOverride All


By default the use of .htaccess files is disabled. WordPress and many WordPress plugins use these files extensively for in-directory tweaks to the web server’s behavior.



Next, we can enable mod_rewrite so that we can utilize the WordPress permalink feature:


a2enmod rewrite



root@gemininew:/etc/apache2/sites-available# a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2
root@gemininew:/etc/apache2/sites-available# systemctl restart apache2


This allows you to have more human-readable permalinks to your posts, like the following two examples:


The a2enmod command calls a script that enables the specified module within the Apache configuration.


next, check to make sure we haven’t made any syntax errors by running the following test.


apache2ctl configtest



root@gemininew:/etc/apache2/sites-available# apache2ctl configtest
AH00558: apache2: Could not reliably determine the server’s fully qualified domain name, using Set the ‘ServerName’ directive globally to suppress this message
Syntax OK



Ensure apache is running with


a2ensite <sites-enabled_configfile>


systemctl enable apache2
systemctl start apache2


Install Mysql/MariaDB


apt install mariadb-server


During the installation you’ll be requested to set a mysql server admin user password.


If the secure installation utility does not run automatically during the installation process, then you can run it explicitly:


mysql_secure_installation utility


Next install the php-mysql module:


root@gemininew:~# apt-get install php-mysql
Reading package lists… Done
Building dependency tree
Reading state information… Done
php-mysql is already the newest version (2:7.4+75).



Enable the mysql service and start it:




root@gemini:/var/lib/mysql# systemctl start mysql
root@gemini:/var/lib/mysql# systemctl status mysql
● mysql.service – LSB: Start and stop the mysql database server daemon
Loaded: loaded (/etc/init.d/mysql; generated)
Active: active (running) since Thu 2022-03-03 13:24:50 UTC; 4s ago
Docs: man:systemd-sysv-generator(8)
Process: 4328 ExecStart=/etc/init.d/mysql start (code=exited, status=0/SUCCESS)
Tasks: 33 (limit: 2274)
Memory: 64.0M
CGroup: /system.slice/mysql.service
├─4367 /bin/sh /usr/bin/mysqld_safe
├─4483 /usr/sbin/mysqld –basedir=/usr –datadir=/var/lib/mysql –plugin-dir=/usr/lib/x86_64-linux-gnu/mariadb19/plugin –user=mysql –skip-log-error –pid-file=/run/mysqld/ –socket=/var/run/mysqld/mysqld.sock
└─4484 logger -t mysqld -p daemon error


Mar 03 13:24:50 gemini /etc/mysql/debian-start[4538]: Upgrading MySQL tables if necessary.
Mar 03 13:24:50 gemini mysqld[4484]: 2022-03-03 13:24:50 10 [Warning] Access denied for user ‘debian-sys-maint’@’localhost’ (using password: YES)
Mar 03 13:24:50 gemini /etc/mysql/debian-start[4541]: Looking for ‘mysql’ as: /usr/bin/mysql
Mar 03 13:24:50 gemini /etc/mysql/debian-start[4541]: Reading datadir from the MariaDB server failed. Got the following error when executing the ‘mysql’ command line client
Mar 03 13:24:50 gemini /etc/mysql/debian-start[4541]: ERROR 1045 (28000): Access denied for user ‘debian-sys-maint’@’localhost’ (using password: YES)
Mar 03 13:24:50 gemini /etc/mysql/debian-start[4541]: FATAL ERROR: Upgrade failed
Mar 03 13:24:50 gemini /etc/mysql/debian-start[4546]: Checking for insecure root accounts.
Mar 03 13:24:50 gemini mysqld[4484]: 2022-03-03 13:24:50 11 [Warning] Access denied for user ‘debian-sys-maint’@’localhost’ (using password: YES)
Mar 03 13:24:50 gemini mysql[4549]: ERROR 1045 (28000): Access denied for user ‘debian-sys-maint’@’localhost’ (using password: YES)
Mar 03 13:24:51 gemini mysqld[4484]: 2022-03-03 13:24:51 12 [Warning] Access denied for user ‘wordpressuser’@’localhost’ (using password: YES)
lines 3-22/22 (END)



and we now have the socket for mysqld:


root@gemini:/run/mysqld# ls -lias
total 4
586 0 drwxr-xr-x 2 mysql root 80 Mar 3 13:24 .
2 0 drwxr-xr-x 29 root root 880 Mar 3 13:20 ..
678 4 -rw-rw—- 1 mysql mysql 5 Mar 3 13:24
677 0 srwxrwxrwx 1 mysql mysql 0 Mar 3 13:24 mysqld.sock



Test the server:


root@gemininew:~# mysqladmin -p -u root version
Enter password:
mysqladmin Ver 8.0.28-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
Copyright (c) 2000, 2022, Oracle and/or its affiliates.


Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective


Server version 8.0.28-0ubuntu0.20.04.3
Protocol version 10
Connection Localhost via UNIX socket
UNIX socket /var/run/mysqld/mysqld.sock
Uptime: 6 min 48 sec


Threads: 2 Questions: 14 Slow queries: 0 Opens: 130 Flush tables: 3 Open tables: 49 Queries per second avg: 0.034




Run the mysql secure installation utility







This will also set the root password for mysql



You have to set the root password here:


root@gemini:/run/mysqld# /usr/bin/mysql_secure_installation




In order to log into MariaDB to secure it, we’ll need the current
password for the root user. If you’ve just installed MariaDB, and
you haven’t set the root password yet, the password will be blank,
so you should just press enter here.


Enter current password for root (enter for none): ***NOTE**** if you try to enter the password for root here it gives an error at this stage:!!! see below
ERROR 1045 (28000): Access denied for user ‘root’@’localhost’ (using password: YES)
Enter current password for root (enter for none): ****PRESS ENTER***- you set the root password further below!***
OK, successfully used password, moving on…


Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.


Set root password? [Y/n] Y
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
… Success!


By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them. This is intended only for testing, and to make the installation
go a bit smoother. You should remove them before moving into a
production environment.


Remove anonymous users? [Y/n]
… Success!


Normally, root should only be allowed to connect from ‘localhost’. This
ensures that someone cannot guess at the root password from the network.


Disallow root login remotely? [Y/n]
… Success!


By default, MariaDB comes with a database named ‘test’ that anyone can
access. This is also intended only for testing, and should be removed
before moving into a production environment.


Remove test database and access to it? [Y/n]
– Dropping test database…
… Success!
– Removing privileges on test database…
… Success!


Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.


Reload privilege tables now? [Y/n]
… Success!


Cleaning up…


All done! If you’ve completed all of the above steps, your MariaDB
installation should now be secure.


Thanks for using MariaDB!




root@gemini:~# mysqlshow -u root -p
Enter password:
| Databases |
| gitea |
| information_schema |
| kevwells |
| mysql |
| nextcloud |
| performance_schema |
| phpmyadmin |
| wordpress |


root@gemini:~# mysqldump -u root -p –all-databases > allgeminidatabases-backup.sql
Enter password:



to restore


mysql -u root -p < allgeminidatabases-backup.sql


root@gemininew:~# mysql -u root -p < allgeminidatabases-backup.sql
Enter password:


root@gemininew:~# mysqlshow -u root -p
Enter password:
| Databases |
| gitea |
| information_schema |
| kevwells |
| mysql |
| performance_schema |
| sys |



Set up a Mysql Database for WordPress


Create an exclusive database for WordPress to control. This can have any name, but we will use the standard name wordpress for this example.


Enter the mysql admin client, using:



root@gemininew:~# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 319
Server version: 10.3.34-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04


Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.


Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.


MariaDB [(none)]>


then do the following:


MariaDB [(none)]> CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Query OK, 1 row affected (0.001 sec)

MariaDB [(none)]> use wordpress;
Database changed
MariaDB [wordpress]>

MariaDB [(none)]> CREATE USER ‘wordpressuser’@localhost IDENTIFIED BY ‘*********’;  
Query OK, 0 rows affected (0.000 sec)


MariaDB [(none)]> GRANT ALL ON wordpress.* TO ‘wordpressuser’@’localhost’;
Query OK, 0 rows affected (0.001 sec)


Query OK, 0 rows affected (0.001 sec)


MariaDB [(none)]>



you can now exit mariadb:


MariaDB [(none)]> exit



Install WordPress


next, download and install wordpress:


you can do a wget from


and then unpack at /var/www/wordpress


you can then create a /var/www/html -> /var/www/wordpress link


and set permissions as follows:


chown -R www-data:www-data /var/www/wordpress


Next we’ll run two find commands to set the correct permissions on the WordPress directories and files:


root@gemininew:~# find /var/www/wordpress/ -type d -exec chmod 750 {} \;
root@gemininew:~# find /var/www/wordpress/ -type f -exec chmod 640 {} \;


Next, modify some of the database connection settings, adjusting database name, the database user, and the associated password you configured within MySQL.


Also set the method WordPress should use to write to the filesystem, set this filesystem method to “direct”.


If we dont do this it could mean WordPress prompting for FTP credentials for some actions:



. . .

// ** MySQL settings – You can get this info from your web host ** //
/** The name of the database for WordPress */
define( ‘DB_NAME’, ‘wordpress’ );


/** MySQL database username */
define( ‘DB_USER’, ‘wordpressuser’ );


/** MySQL database password */
define( ‘DB_PASSWORD’, ‘***password commented out***’ );


/** MySQL hostname */
define( ‘DB_HOST’, ‘localhost’ );


/** Database Charset to use in creating database tables. */
define( ‘DB_CHARSET’, ‘utf8’ );


/** The Database Collate type. Don’t change this if in doubt. */
define( ‘DB_COLLATE’, ” );


. . .

define(‘FS_METHOD’, ‘direct’);



NOTE: we substitute the above with our own password as created for the wordpressuser in mariadb! (not shown here for security reasons)


next, in web-browser, open the http://servername/wp-admin link


and the wordpress admin dashboard initial setup page should appear.




Firewalling for apache


The following ports need to be opened, port 443 and 80.


root@gemini:/etc/apache2# ufw status
Status: active


To Action From
— —— —-
22 ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
3306 ALLOW Anywhere
9993 ALLOW Anywhere
2049 ALLOW Anywhere
22 (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
3306 (v6) ALLOW Anywhere (v6)
9993 (v6) ALLOW Anywhere (v6)
2049 (v6) ALLOW Anywhere (v6)




DO NOT open port 444!


NOTE that port 444 is NOT opened on the firewall – this is used internally by apache ie behind the firewall, between apache and sslh. It should not be accessible from outside.



MySql/MariaDB has to be installed and up and running.


Check it is enabled and running using


systemctl enable mysql
systemctl start mysql
systemctl status mysql


During installation, all being well, mysql/mariadb will start automatically, unless the port used by mysql is already in use.






Install SSL Certificates


SSL/TLS https certificates supplied by Lets Encrypt


The apache webserver needs to be running on port 80 to carry out registration to obtain the certificates for each virtual host.


Certificates are required for all virtual websites operating on the IP with https:,


Use the certbot command-line utility to request the SSL certificates for the websites. You need to run certbot in turn for each virtual host website you are operating on https.


Make sure /etc/apache2/ports.conf is set to listen on port 80 and 443 initially for ssl.


After obtaining the certificates, and since we are using the sslh multiplexer, you need to change ports.conf all references to Listen 443 port to Listen 444 as this is the port used for sslh multiplexer.


The sslh multiplexer separates out incoming traffic on 443 to 444 for Apache and 22 for ssh.


Install wordpress under /var/www/wordpress


then create symbolic links as follows:


/var/www/html -> /var/www/


/var/www/ -> /var/www/wordpress


Configure the wp-config.php file:


root@gemini:/var/www/wordpress# cat wp-config.php
//Begin Really Simple SSL session cookie settings
@ini_set(‘session.cookie_httponly’, true);
@ini_set(‘session.cookie_secure’, true);
@ini_set(‘session.use_only_cookies’, true);
//END Really Simple SSL


* The base configuration for WordPress
* The wp-config.php creation script uses this file during the installation.
* You don’t have to use the web site, you can copy this file to “wp-config.php”
* and fill in the values.
* This file contains the following configurations:
* * MySQL settings
* * Secret keys
* * Database table prefix
* @link
* @package WordPress



!!!!! DB_NAME and other variables have been omitted here for security reasons!!!!



// ** MySQL settings – You can get this info from your web host ** //
/** The name of the database for WordPress */
define( ‘DB_NAME’, ‘***’ );


/** MySQL database username */
define( ‘DB_USER’, ‘***’ );


/** MySQL database password */
define( ‘DB_PASSWORD’, ‘***’ );


/** MySQL hostname */
define( ‘DB_HOST’, ‘localhost’ );


/** Database charset to use in creating database tables. */
define( ‘DB_CHARSET’, ‘utf8mb4’ );


/** The database collate type. Don’t change this if in doubt. */
define( ‘DB_COLLATE’, ” );


* Authentication unique keys and salts.
* Change these to different unique phrases! You can generate these using
* the {@link secret-key service}.
* You can change these at any point in time to invalidate all existing cookies.
* This will force all users to have to log in again.
* @since 2.6.0
define( ‘AUTH_KEY’, ‘;




* WordPress database table prefix.
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
$table_prefix = ‘wp_’;


* For developers: WordPress debugging mode.
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
* For information on other constants that can be used for debugging,
* visit the documentation.
* @link
define( ‘WP_DEBUG’, false );


/* define( ‘WP_DEBUG’, false ); */


/* Add any custom values between this line and the “stop editing” line. */



/* That’s all, stop editing! Happy publishing. */


/** Absolute path to the WordPress directory. */
if ( ! defined( ‘ABSPATH’ ) ) {
define( ‘ABSPATH’, __DIR__ . ‘/’ );


/** Sets up WordPress vars and included files. */
require_once ABSPATH . ‘wp-settings.php’;


/* define(‘WP_ALLOW_REPAIR’, true); */


define(‘FS_METHOD’, ‘direct’);


@ini_set( ‘upload_max_filesize’ , ‘12800M’ );
@ini_set( ‘post_max_size’, ‘12800M’);
@ini_set( ‘memory_limit’, ‘256M’ );
@ini_set( ‘max_execution_time’, ‘300’ );
@ini_set( ‘max_input_time’, ‘300’ );


define(‘FORCE_SSL_ADMIN’, true);


define( ‘WP_HOME’, ‘’ );
define( ‘WP_SITEURL’, ‘’ );








Install Nextcloud


How to configure Nextcloud


Nextcloud has to be downloaded and installed under /var/www/nextcloud


enter a database name for nextcloud – usually “nextcloud”


port is localhost:3306


a database username and a password.


this gets saved in the nextcloud config file under /var/www/nextcloud/config/config.php


Create a virtual host for nextcloud in /etc/apache2/sites-available/<apacheconfigfile>.conf


You will also need to define the virtual host ie nextcloud for http port 80 in order to obtain the SSL/TLS certificate from Lets Encrypt.


Once this is obtained and installed, you can then delete this port 80 http virtual host for nextcloud.




Switch WordPress from http to https


Once you have obtained and installed the SSL certificates for the virtual hosts, you can switch wordpress to https.


In practice, the switch from http to https for wordpress was complicated.


There are two aspects to this: apache, and wordpress.


The apache virtual host definitions are relatively straightforward. See the apache section above.


WordPress is a little more involved and complicated.


First, you need to install the wordpress plugin Really Simple SSL (if only it were “really simple” in practice).


It was difficult trying to switch the wordpress site name in the wordpress dashboard from http to https.


Also you need to save permalinks twice, using the Permalinks plugin which must first be installed.


Trying to change the wordpress site definitions in the wordpress dashboard under settings – general, did not work, wordpress kept changing them back.


I also tried changing the site names using the mysql admin client and sql commands, but these too would get changed back!


Finally, I had to add the two definitions to the wp-config.php and also the FORCE_SSL_ADMIN directive:


define(‘FORCE_SSL_ADMIN’, true);


define( ‘WP_HOME’, ‘’ );
define( ‘WP_SITEURL’, ‘’ );



Then after numerous attempts, it finally accepted the change from http to https.


You also have to save the links again in WordPress Permalinks plugin after changing from http to https:


select plain, then back to custom, then plain, then finally set to custom.


The links should then function correctly.



Also in this case, don’t forget we are using sslh multiplexer, so the virtual host definitions for the ports need to be 444 for https and not 443.


Make sure these are set in the sites-enabled config file as well as in the apache ports.conf file! (Lets Encrypt changes then to 443 – you have to change them back manually!).


Then make sure sslh is running correctly, listening on 443, and passing ssh traffic to 22 sshd, and https traffic on to 444 apache.



Firewalling – Portmapper


Make sure port 111 for nfs portmapper is closed.


Portmapper is a service usually used with NFS. When this is not properly firewalled, it can be abused to conduct DDOS attacks.


All portmapper services should therefore be behind a firewall and restricted to IPs that need to contact them.


For Linux machines, add firewall rules to block port 111 on both UDP and TCP as follows:


iptables -I INPUT 1 -m tcp -p tcp –dport 111 -j DROP
iptables -I INPUT 1 -m udp -p udp –dport 111 -j DROP


Alternatively, set using the ufw firewall utility in ubuntu with:


root@gemini:# ufw deny 111
Rule updated
Rule updated (v6)
root@gemini:~# ufw status
Status: active


To Action From
— —— —-
22 ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
3306 ALLOW Anywhere
9993 ALLOW Anywhere
111 DENY Anywhere
2049 ALLOW Anywhere
22 (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
3306 (v6) ALLOW Anywhere (v6)
9993 (v6) ALLOW Anywhere (v6)
111 (v6) DENY Anywhere (v6)
2049 (v6) ALLOW Anywhere (v6)





Modify hosts files


The hosts file on the new server and all connecting clients must be updated to point the server name to the new IP address.


The new server machine name is changed from gemininew to gemini.


Modify /etc/hosts files to define the new machine IP with the server localhost name ie gemini


Distribute the new /etc/hosts to all connecting clients. This can be done using scp.




Switch off old virtual machine


The final task is to switch off and delete the old virtual machine.


This does not actually have to be switched off in order to delete, but to keep things clean in the event of having to request a special restore from the server provider we will switch off correctly first and then delete.


Login on the OLD machine,


Check the ip address of the machine to make absolutely sure you are logged in on the OLD machine:




then at the command line, do a:


shutdown -h now


Then switch off the old machine on the virtual server provider account dashboard.


Next you can delete the old virtual server.


Make absolutely sure first that you are clicking on and deleting the correct machine!



Finally, delete any associated snapshot backups associated with the old machine.


With that the server migration is now complete. You’ve earned a good cup of coffee!











Table of Contents