This guide describes how to deploy a PHP application over SFTP on a server with PHP and MySQL installed, using the PHP development server.
- Legend
- ❗ Setup
- ❗ Use a real password
- ❗ Upload the application
- ❗ Initialize the database
- ❗ Update the configuration
- ❗ Run the PHP development server
- 🏁 What have I done?
- 💥 Troubleshooting
Parts of this guide are annotated with the following icons:
- ❗ A task you MUST perform to complete the exercise.
- ❓ An optional step that you may perform to make sure that everything is working correctly.
⚠️ Critically important information about the exercise.- 💎 Tips on the exercise, reminders about previous exercises, or explanations about how this exercise differs from the previous one.
- 👾 More advanced tips on how to save some time. Challenges.
- 📚 Additional information about the exercise or the commands and tools used.
- 🏁 The end of the exercise.
- 🏛️ The architecture of what you deployed during the exercise.
- 💥 Troubleshooting tips: how to fix common problems you might encounter.
Use the previous PHP Todolist Exercice. Clone the PHP Todolist Exercice on your local machine if you do not have it. Be sure to use a version with the three SQL queries implemented.
Connect to your server with SSH.
Update your package lists and install the MySQL database server:
$> sudo apt update
$> sudo apt install mysql-server
📚 Advanced Packaging Tool (APT) is the package manager for Ubuntu (and some other Linux distributions). We will not discuss this tool, but you can read more about it in the installation & upgrading section of the system administration cheatsheet.
💎 APT may prompt you to restart some services. See the troubleshooting section about 💥 Daemons using outdated libraries if necessary.
APT should automatically start MySQL after installation. You can check this with the following command:
$> sudo systemctl status mysql
Secure your installation by running the mysql_secure_installation
tool that
comes with MySQL. It will ask you several questions to help you improve the
security of your MySQL installation:
$> sudo mysql_secure_installation
Securing the MySQL server deployment.
Connecting to MySQL using a blank password.
VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?
Press y|Y for Yes, any other key for No: y
There are three levels of password validation policy:
LOW Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary file
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Please set the password for root here.
New password: ***
Re-enter new password: ***
Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL 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? (Press y|Y for Yes, any other key for No) : y
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? (Press y|Y for Yes, any other key for No) : y
Success.
By default, MySQL 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? (Press y|Y for Yes, any other key for No) : y
- 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? (Press y|Y for Yes, any other key for No) : y
Success.
All done!
Here you will install the bare minimum:
Simply run this command to install both:
$> sudo apt install php-fpm php-mysql
📚 Traditionally, PHP is deployed using the Apache web server, which is a generic web server and reverse proxy but is also capable of executing PHP code. To simplify things in this exercise, we will not install Apache, but instead execute the PHP application directly from the command line using the simpler PHP development server.
The todolist.sql
file creates a todolist
user with the password
change-me-now
by default. You should change the password to a more secure
value. Make sure that the password you choose is strong enough per the minimum
password requirements you chose when you secured the MySQL installation.
💎 Need help choosing a good password? Don't use something that is hard to remember. You're better off using a passphrase (here's a French version).
📚 It is good practice to create a different user and password for each application that connects to the MySQL database server. That way, if one of the applications is compromised, it cannot access or modify the databases of the other applications (provided you configured appropriate access privileges).
Notably, you should never use the MySQL
root
password to connect an application to its database. You, the system administrator, should be the only person who knows that password.
On your local machine, use an SFTP client like FileZilla or Cyberduck (macOS) to upload the application to the server.
Connect the SFTP client to your server using SSH public key authentication. In FileZilla, open the Site Manager and configure your connection like this:
💎 You must select your private key (
id_rsa
and notid_rsa.pub
) in FileZilla. The server you are connecting to has your public key. Just like when you use SSH on the command line, FileZilla will use your private key to prove to the server that you are the owner of your public key.💎 On Windows, you can toggle the display of hidden files in the View tab of the explorer to access your
.ssh
directory manually. On macOS, typeopen ~/.ssh
in your Terminal or use theCmd-Shift-.
shortcut to display hidden files. On most Linux distributions, the file manager will have an option to show hidden files under its menu.💎 On Windows, FileZilla may ask you to convert your private key to another format. You can do so.
Once you are connected to your server with your SFTP client, copy the
application to /home/john_doe/todolist
(replacing john_doe
with your Unix
username).
In FileZilla, you can simply drag-and-drop the directory from your machine on the left to the server on the right. You can then rename it if necessary.
Connect to your server and go into the uploaded directory:
$> hostname
john-doe.archidep.ch
$> cd ~/todolist
Execute the project's SQL file to create the database and table (it will ask you
for the MySQL root
user's password you defined earlier):
$> sudo mysql < todolist.sql
📚 This uses the Unix redirection operator
<
to send the contents of thetodolist.sql
file into the standard input stream of thesudo mysql
command. When provided with SQL queries on its input stream, themysql
command will connect to the MySQL server and execute them, then stop.
To make sure everything worked, you can check that the table was created in the MySQL database server. You do not have a phpMyAdmin web interface to administer the database server, since you are installing everything on your server yourself, and you did not install that.
Use the following command and SQL queries to first connect to the MySQL database
server as the administrator (the MySQL root
user), then display the todo
table's schema:
$> sudo mysql
> connect todolist;
> show create table todo;
+-------+----------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------+
| todo | CREATE TABLE `todo` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(2048) NOT NULL,
`done` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------+
1 row in set (0.00 sec)
Everything went well if the table was created, since the creation of that table is the last step of the
todolist.sql
script.
You may exit the interactive MySQL console like most shells by typing exit
.
Update the first few lines of the index.php
file with the correct configuration:
define('BASE_URL', '/');
define('DB_USER', 'todolist');
define('DB_PASS', 'your-secret-password');
define('DB_NAME', 'todolist');
define('DB_HOST', '127.0.0.1');
define('DB_PORT', '3306');
💎 The
index.php
file on the server must be modified. There are several ways you can do this:
- Edit the file locally, then copy it to the server again using your SFTP client like FileZilla.
- Edit the file directly on the server with
nano
orvim
.- Some SFTP clients allow you to open a remote file in your local editor. In FileZilla, right-click a file, select View/Edit, then choose your favorite editor. Make your changes and save the file. FileZilla should automatically prompt you to upload the changes.
Also in the uploaded directory on the server, run a PHP development server on port 3000:
$> php -S 0.0.0.0:3000
📚 The
-S <addr:port>
option of thephp
command starts the built-in web server on the given local address and port.💎 You must really use
0.0.0.0
for thephp -S
command, and not your server's IP address.0.0.0.0
is not an actual IP address; it is a special notation that tells the PHP development server to accept connections from any IP address.
You (and everbody else) should be able to access the application in a browser at
your server's IP address and the correct port (e.g. http://W.X.Y.Z:3000
).
You have deployed a PHP application to a server running in the Microsoft Azure cloud.
The application is now publicly accessible by anyone on the Internet, at your instance's public IP address.
This is a simplified architecture of the main running processes and communication flow at the end of this exercise:
Compare this with the same exercise deployed on your local machine.
Here's a few tips about some problems you may encounter during this exercise.
When you install a package with APT (e.g. MySQL), it may prompt you to reboot and/or to restart outdated daemons (i.e. background services):
Simply select "Ok" by pressing the Tab key, then press Enter to confirm.
📚 This happens because most recent Linux versions have unattended upgrades: a tool that automatically installs daily security upgrades on your server without human intervention. Sometimes, some of the background services running on your server may need to be restarted for these upgrades to be applied.
Since you are installing a new background service (the MySQL server) which must be started, APT asks whether you want to apply upgrades to other background services by restarting them. Rebooting your server would also have the effect of restarting these services and applying the security upgrades.
You may encounter this error when mysql_secure_installation
prompts you to set
the password for the MySQL root
user:
$> sudo mysql_secure_installation
...
Please set the password for root here.
New password:
Re-enter new password:
Estimated strength of the password: 50
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
... Failed! Error: SET PASSWORD has no significance for user 'root'@'localhost'
as the authentication method used doesn't store authentication data in the
MySQL server. Please consider using ALTER USER instead if you want to
change authentication parameters.
This is a bug that exists with the latest versions of
mysql_secure_installation
and recent Ubuntu installations (since July 2022).
If you encounter this bug, mysql_secure_installation
will be stuck in a loop.
Close your terminal window and connect to your server again in another
terminal.
Connect to the MySQL server:
$> sudo mysql
Your prompt should change to reflect the fact that you are connected to the MySQL server. You can then run the following query:
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
Then exit the MySQL server:
mysql> exit
You can now re-run the original command:
$> sudo mysql_secure_installation
Once it is done, you can reconfigure MySQL to use passwordless
socket authentication (it will ask you for the MySQL root
password you have just defined):
$> sudo mysql -p
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;
mysql> exit
If socket authentication is correctly configured, you should now be able to
connect as the MySQL root
user without a password with sudo
:
$> sudo mysql
mysql> exit
📚 This does not mean that anyone can access MySQL without a password. You can do so because you are using
sudo
and have just configured MySQL to use socket authentication for itsroot
user.There are two sets of users here:
- Your server has a number of Unix users (defined in
/etc/passwd
), one of them being the Unixroot
user.- The MySQL database server has its own list of MySQL users independent of the system. There is also a MySQL user named
root
by default.By default, the
mysql
command will attempt to connect as the MySQL user with the same name as the Unix user running the command. You can also specify which user to connect as with the-u
(user) option:$> whoami john_doe $> mysql # connect to MySQL as the MySQL "john_doe" user (because # that is the name of the Unix user running the command) $> mysql -u alice # connect to MySQL as the MySQL "alice" user $> sudo mysql # connect as the MySQL `root` user (since you temporarily # become the Unix "root" user when using "sudo") $> sudo mysql -u root # equivalent to the previous commandThe first two
mysql
commands will probably fail:ERROR 1045 (28000): Access denied for user 'john_doe'@'localhost' (using password: NO) ERROR 1045 (28000): Access denied for user 'alice'@'localhost' (using password: NO)
This is because MySQL has no
john_doe
oralice
users (unless you created them yourself). It may also be because you are trying to connect as a MySQL user who has a password. In this case, you should add the-p
(password) option to have MySQL prompt you for the password when connecting (e.g.mysql -u alice -p
).If you followed the instructions above, you have replaced password authentication for the MySQL
root
user with the socket authentication method which delegates authentication to the Unix system. With this method, MySQL will compare the username of the Unix user running themysql
command with the MySQL user you are trying to connect as. It will only allow the connection if both are the same. In this case, since you are the Unixroot
user when usingsudo
, the MySQL server will allow a connection as the MySQLroot
user (you will not have to enter a password).(Source of the solution: https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-22-04)
If you see this error after running a sudo mysql
command:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
It means that your MySQL server is configured to require a password for the
root
user. You have two choices:
-
Either add the
-p
option to allmysql
commands. It will then prompt you for the MySQLroot
password (that you defined when runningmysql_secure_installation
). -
Or, configure MySQL to use socket authentication for the
root
user (the following command will ask you for the MySQLroot
password you defined when runningmysql_secure_installation
):$> sudo mysql -p mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket; mysql> exit
If socket authentication is correctly configured, you should now be able to connect as the MySQL
root
user without a password withsudo
:$> sudo mysql mysql> exit
An error may occur when you execute the SQL queries in the todolist.sql
script. For example, MySQL may tell you the todolist
user's password in the
script is not strong enough, depending on the settings you selected when
securing the MySQL installation.
To start over from scratch, connect to the MySQL server as an administrator and type the following queries:
$> sudo mysql
> drop table todolist.todo;
> drop user todolist@localhost;
> drop database todolist;
💎 Some of these commands may cause errors if the
todolist.sql
script could not execute entirely. For example, if the script could not create thetodolist
user and/or thetodo
table, the firstdrop table todolist.todo;
query will fail with:
ERROR 1051 (42S02): Unknown table 'todolist.todo'
That's fine. Running the three queries will make sure you have nothing left that may have been created by the
todolist.sql
script, so you can start over with a clean state.
Once you have dropped everything, you can resume the exercise from the database initialization step.
If you get an HTTP 500 error (which means an internal server error),
look at the PHP development server logs in the terminal where you are running
the php -S 0.0.0.0:3000
command. You will probably see something like this:
$> php -S 0.0.0.0:3000
[Thu Oct 20 09:29:40 2022] PHP 8.1.2 Development Server (http://0.0.0.0:3000) started
[Thu Oct 20 09:29:41 2022] 213.3.2.128:44496 Accepted
[Thu Oct 20 09:29:41 2022] PHP Fatal error: Uncaught PDOException: SQLSTATE[HY000] [1045] Access denied for user 'todolist'@'localhost' (using password: YES) in /home/john_doe/todolist/index.php:17
Stack trace:
#0 /home/john_doe/todolist/index.php(17): PDO->__construct()
#1 {main}
thrown in /home/john_doe/todolist/index.php on line 17
[Thu Oct 20 09:29:41 2022] 213.3.2.128:44496 [500]: GET / - Uncaught PDOException: SQLSTATE[HY000] [1045] Access denied for user 'todolist'@'localhost' (using password: YES) in /home/john_doe/todolist/index.php:17
Stack trace:
#0 /home/john_doe/todolist/index.php(17): PDO->__construct()
#1 {main}
thrown in /home/john_doe/todolist/index.php on line 17
[Thu Oct 20 09:29:41 2022] 213.3.2.128:44496 Closing
[Thu Oct 20 09:29:41 2022] 213.3.2.128:44495 Accepted
If you see this Access denied for user 'todolist'@'localhost' (using password: YES)
in the logs, it means that MySQL is not allowing the PHP todolist to open
a MySQL connection as the todolist
user. The using password: YES
part
indicates that a password is sent, but it is incorrect.
You may be using the wrong password. Make sure the DB_PASS
constant at the top
of the index.php
file on the server contains the correct password. This must
be the password that was in the todolist.sql
file when you executed it.
If you do not remember the password, follow the troubleshooting instructions for an error running
todolist.sql
to re-create the database, user and password.