Run a website on shared hosting

The account and the plan

Shared hosting is sold as a plan for one account. The plan fixes how many domains you may add, how much disk space and traffic you get, how many databases and mailboxes are included, and which PHP versions you may choose. The panel is the place where all of it is configured.

Before you pay, check five things, because they are painful to change later: SSH or at least SFTP access, the PHP versions offered, the number of databases, the size of the mailboxes, and whether the provider makes backups and lets you restore one yourself. Which hosting to choose covers the rest.

When the account arrives you get three addresses: the panel, the server name or IP for file transfer, and a temporary address such as server17.hosting.example where the site answers before the domain is connected. The temporary address is useful: you can build and check the site there while the DNS still points elsewhere.

Add the site

  1. Log in to the panel with the password from the welcome email, and change it at once.
  2. Find the sites or websites section and click Add site.
  3. Type the domain without http and without a trailing slash: example.com.
  4. Accept the suggested folder, for example /home/example/www/example.com, or write your own. Remember it: it is where the files go.
  5. Choose PHP 8.3, or the newest version the plan offers.
  6. Save, then add www.example.com as an alias of the same site if the panel asks for it separately.

The panel now creates the folder, a system user for it, a web server configuration block and, if you allowed it, a DNS zone. The site is reachable at the temporary address, usually with the domain name in the Host header trick that nginx.conf: configuration and sites explains for Nginx.

Upload the files

There are three ways to get files onto shared hosting: SFTP, the file manager in the panel, and FTP. Use SFTP. It encrypts the password and the files, it works over the same port as SSH, and every panel supports it. FTP sends both in clear text, and many providers disable it for that reason.

On your computer, open a terminal in the folder with the site and connect:

sftp example@203.0.113.10

Connected to 203.0.113.10. sftp>

The local copy of the site is the folder ~/site/, with config.php and the html/ folder inside. Upload config.php one level above the site folder, so that the web server cannot serve it, and upload the contents of html/ into the site folder. The dot at the end of put -r . means the current local folder:

sftp> cd www sftp> put ~/site/config.php sftp> cd example.com sftp> lcd ~/site/html sftp> put -r . sftp> bye

A graphical client such as FileZilla or WinSCP does the same with two panels: drag the files from the left, your computer, to the right, the server. Set the protocol to SFTP, not FTP. If you prefer one command for the whole folder, scp copies it in two steps:

scp ~/site/config.php example@203.0.113.10:~/www/

scp -r ~/site/html/. example@203.0.113.10:~/www/example.com/

The file manager in the panel is fine for a single file, a config edit or a quick look at permissions. It is slow for a whole site and it may time out on a large archive. Scripted transfers are covered in SFTP from a Bash script: a few lines of Bash copy a folder on a schedule.

The first page and a PHP test

Start with one static file. Create index.html on your computer, upload it, and open the site:

curl -I http://example.com/

HTTP/1.1 200 OK Server: nginx Content-Type: text/html

A 200 with the right content type means the folder, the file and the domain all agree. If the browser shows a directory listing instead, the file is not named index.html or it sits one folder too high. If it shows the provider default page, the domain is not attached to this folder.

Then check PHP before you build the page that uses it. Create test.php with three lines, upload it and open http://example.com/test.php:

nano test.php

<?php echo "PHP " . PHP_VERSION; echo ", " . ini_get("memory_limit") . " memory";

PHP 8.3.6, 256M memory

The output proves that the web server passed the request to PHP and that the version is the one you chose. Delete the file at once: a page that prints server details helps an attacker more than it helps you. Do not use phpinfo() for this test; the three lines above give what you need. If the browser offers to download the file instead of running it, PHP is not enabled for the site, and the panel has a setting for the PHP version of the domain.

A database and a PHP page that uses it

The example site is one table and one PHP file. In the databases section of the panel create a database, a user and a password, and grant the user access to that database. The panel often prefixes the names with your account name; accept what it produces and write the real names down. Here they are example_db and example_user.

Then create the table and one row, in phpMyAdmin or in the SQL tab of the database:

CREATE TABLE pages ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, body TEXT NOT NULL ); INSERT INTO pages (title, body) VALUES ('Hello', 'This page came from the database.');

The table has an id, a title and a body, which is all a small site needs to start. The site itself is two files. config.php keeps the four connection values one level above the site folder, where the web server cannot serve it:

nano config.php

<?php return [ 'db_host' => 'localhost', 'db_name' => 'example_db', 'db_user' => 'example_user', 'db_pass' => 'a-long-random-password', ];

html/index.php loads that file, reads the first row with a prepared statement, and prints it with htmlspecialchars, so that a title containing HTML cannot inject tags:

nano html/index.php

<?php $config = require __DIR__ . '/../config.php'; $pdo = new PDO( 'mysql:host=' . $config['db_host'] . ';dbname=' . $config['db_name'] . ';charset=utf8mb4', $config['db_user'], $config['db_pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); $stmt = $pdo->prepare('SELECT title, body FROM pages ORDER BY id LIMIT 1'); $stmt->execute(); $page = $stmt->fetch(PDO::FETCH_ASSOC); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title><?= htmlspecialchars($page['title']) ?></title> </head> <body> <h1><?= htmlspecialchars($page['title']) ?></h1> <p><?= nl2br(htmlspecialchars($page['body'])) ?></p> </body> </html>

Upload the two files as the previous section showed, open http://example.com/ and you should see the row from the table:

curl -s http://example.com/ | grep -E '<h1>|<p>'

<h1>Hello</h1> <p>This page came from the database.</p>

A database error here means the four values in config.php do not match the panel, or the user was not granted access to the database. The password lives outside the web root on purpose: a config.php inside the site folder would be served to anyone who guessed its name.

Anything the site saves goes into html/uploads/. That folder is for files, never for PHP, and lesson 7 shows the rule that refuses to run code there.

If your site is WordPress, the side story lists the extra steps: Side story: WordPress (optional).

HTTPS and the redirects

Issue the certificate as the previous lesson describes, so that the site answers over HTTPS, then switch on two redirects: every http request goes to https, and one of the two names goes to the other. Many panels have both as checkboxes. If yours does not, the rules go in a file called .htaccess in the site folder, which Apache reads on every request. With SSH access:

nano .htaccess

RewriteEngine On RewriteCond %{HTTPS} !=on RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]

The first block sends plain HTTP to HTTPS and keeps the requested path. The second takes any name that starts with www. and sends it to the same name without it, over HTTPS. Order matters: a request to http://www.example.com/a passes both rules and ends at https://example.com/a.

On Nginx there is no .htaccess; the same redirects are return 301 lines in the server block, and lesson 5 writes them. Check the result with the redirect chain:

curl -I http://www.example.com/about/

HTTP/1.1 301 Moved Permanently Location: https://example.com/about/

One 301 and the final 200 is the goal. A chain of three redirects slows every first visit and confuses search engines. After the switch, check that every image, stylesheet and script on the page is loaded over https:// or with a relative path: a browser blocks an http:// file on an HTTPS page as mixed content, and the page looks broken even though the certificate is fine.

A mailbox

A mailbox on your own domain looks professional and costs nothing extra on most plans. In the mail section of the panel create you@example.com, set a long password, and note the server names the panel shows. They are usually:

SettingValue
IMAP, incomingmail.example.com, port 993, SSL/TLS
SMTP, outgoingmail.example.com, port 465, SSL/TLS, or port 587 with STARTTLS
User nameThe full address, you@example.com

Send a message to an address at another provider and reply to it. If the message lands in spam, the domain is missing SPF and DKIM records: the panel has switches for them, and lesson 9 explains what they mean. Without them, most large mail providers will treat your mail as suspicious, no matter how good the mailbox is. Phishing: spot it in 10 seconds is worth reading before you hand the mailbox to anyone else.

Backups

The provider makes backups, and that is not enough. You need a copy you can reach when the account is locked, the provider has a bad day, or a bad update deletes half the database. Take one after the site works, and then on a schedule.

Files come down over SFTP or as an archive from the panel. The database needs its own export: phpMyAdmin has an export button, and with SSH access one command is enough:

mysqldump -u example_user -p example_db > ~/backup-$(date +%F).sql

The command asks for the database password and writes a text file with every table and row. Download it the same day: a backup that lives only on the server it backs up is not a backup. Keep the last few dumps and delete the old ones, so the folder does not grow without limit. The principles are in Backups that actually restore, and a scheduled job for the dump is a cron line.

The final check

  1. https://example.com answers 200 with the right page.
  2. http://example.com redirects once to HTTPS.
  3. https://www.example.com redirects to the name you chose.
  4. The certificate is valid for both names and does not expire this month.
  5. A PHP page runs, and the test file is deleted.
  6. The page shows the row from the table, which proves the database works.
  7. Mail to and from the mailbox arrives, and the message headers show SPF and DKIM passing.
  8. A backup of files and database is downloaded to your computer, and you can open the archive.
  9. The panel password is long and unique, and two-factor login is on.

That is a working site on shared hosting. It has a domain, HTTPS, a database, mail and a backup. The next lesson does the same on a server where you are the administrator, and most of these nine lines become commands you write yourself.

Quiz

1 Why is SFTP preferred over FTP for uploading a site?

  1. It is faster
  2. It encrypts the password and the files, while FTP sends both in clear text
  3. It compresses the files
  4. It works without a password
Answer

b) FTP has no encryption, so anyone on the network path can read the password. SFTP runs over SSH and encrypts the whole session.

2 The browser offers to download test.php instead of running it. What is wrong?

  1. The file has the wrong permissions for reading
  2. PHP is not enabled for this site, or the file is outside the site folder
  3. The database is down
  4. The certificate has expired
Answer

b) The web server served the file as text, so it never reached a PHP handler. Check the PHP version setting of the domain and the folder the site points to.

3 What four values does a PHP script need to connect to a database?

  1. Domain, IP address, name servers and TTL
  2. Host, database name, database user and password
  3. Mailbox, password, IMAP port and SMTP port
  4. Certificate, key, chain and CA
Answer

b) They go into config.php, which stays outside the web root. The host is localhost when the database runs on the same server as the site.

4 In the .htaccess example, what does the second rule do?

  1. It compresses images
  2. It sends www.example.com to example.com over HTTPS
  3. It blocks visitors from other countries
  4. It creates the certificate
Answer

b) It matches a host that starts with www. and redirects to the same name without it, keeping the path.

5 Mail sent from your new mailbox lands in spam. What is the first thing to fix?

  1. The mailbox password
  2. The SPF and DKIM records of the domain
  3. The IMAP port
  4. The site favicon
Answer

b) Without SPF and DKIM, receivers cannot tell that the message really comes from your domain. The panel has switches for both.

6 The provider keeps daily backups. Why take your own?

  1. Provider backups are always broken
  2. You need a copy you control when the account is locked or the provider has an outage
  3. It makes the site faster
  4. It is required for HTTPS
Answer

b) A backup inside the same account disappears with the account. Download files and a database dump to your own machine, and test that they open.

7 A page on HTTPS shows a broken image, and the browser console says mixed content. What is wrong?

  1. The image address starts with http://, and the browser blocks it on an HTTPS page
  2. The certificate is issued for another name
  3. The database connection failed
  4. Port 25 is closed
Answer

a) An http:// file on an https:// page is mixed content, and browsers refuse to load it. Use an https:// address or a relative path for every image, stylesheet and script.

Article author: Arthur Isaev