This tutorial shows how to install a LAMP stack with suPHP on a clean Ubuntu 14.04 LTS
Update your VPS
Update and upgrade your server by running these commands
sudo apt-get update sudo apt-get upgrade
Save the file and you are done.
Install Apache
Install Apache by running the following command
sudo apt-get install apache2
After doing so visit the public IP address and you should see something like this
Right now, all pages will be under the /var/www/html/ folder.
Create localhost user and default directory skeleton
Run the following commands that creates the user localhost as well as set home directory and password for it.
sudo useradd -d /var/www/localhost -m localhost sudo passwd localhost
Create default directory
cd /var/www/localhost sudo mkdir public_html sudo mkdir logs sudo chown -Rv localhost:localhost /var/www/localhost
Disable shell access as we only want these users to have access via SFTP – see more on this later
sudo usermod localhost -s /bin/false
Create additional users
Run the following commands to add new user with a home directory that is a copy of the /var/www/localhost directory
sudo useradd -d /var/www/sub-one.example.com -m -k "/var/www/localhost" sub-one.example.com sudo passwd sub-one.example.com sudo usermod sub-one.example.com -s /bin/false
Add Virtual Host
With a virtual host you tell Apache to host different sites on different places on your server.
To set up a virtual host copy the default virtual host file. I like to have one for each of my domains but you should be fine if you like to have all your setup in the same file
sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/sub-one.example.com.conf
The default file in my case i called 000-default.conf. It might just be called default.conf in other environments.
Now go and edit the file
sudo nano /etc/apache2/sites-available/sub-one.example.com.conf
It should look something like this – just replace the domain everywhere it occurs. If you wish to allow .htaccess files be sure to set AllowOverride to All in you home directory (in this case /var/www/sub-one.example.com/public_html)
<VirtualHost *:80> ServerName sub-one.example.com #ServerAlias example.com - change this if you wish to map example.com to www.example.com ServerAdmin webmaster@example.com DocumentRoot /var/www/sub-one.example.com/public_html <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/sub-one.example.com/public_html> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog /var/www/sub-one.example.com/logs/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog /var/www/sub-one.example.com/logs/access.log combined </VirtualHost>
For other domains you can now copy this file like this
sudo cp /etc/apache2/sites-available/sub-one.example.com.conf /etc/apache2/sites-available/sub-two.example.com.conf
…and then edit the newly created file to change everywhere the domain name occurs.
To enable each site enter this command for each domain like this
sudo a2ensite sub-one.example.com
Now restart Apache
sudo service apache2 restart
You should now be able to access your sites on the domains you have made virtual host files for. In my case sub-one.example.com and sub-two.example.com. But currently, there is nothing on these sites so go on, and create an index.html in each of the public_html folders like this
sudo nano /var/www/sub-one.example.com/public_html/index.html
write something in each of the – e.g. <h2>sub-one.example.com</h2>. There is no need to write <html> <body> etc. as any modern browser will automatically do so for you. Remeber to create af file like this for each of your domains including localhost.
For the localhost (which shows the site when the public IP is entered in the browser) user it seems like Apache takes the first enabled site. You can either make a 000-localhost.conf file or edit the 000-default.conf file. I have chosen to make a 000-localhost.conf file
sudo cp /etc/apache2/sites-available/sub-one.example.com.conf /etc/apache2/sites-available/000-localhost.conf
Open the file and change all occurrences of sub-one.example.com with localhost. Furthermore, comment out or remove the ServerName so the document looks like this
<VirtualHost *:80> #ServerName localhost #ServerAlias example.com ServerAdmin webmaster@example.com DocumentRoot /var/www/localhost/public_html <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/localhost/public_html> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog /var/www/localhost/logs/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog /var/www/localhost/logs/access.log combined </VirtualHost>
Once you have saved the document you should enabled it using the a2ensite command
sudo a2ensite 000-localhost.conf
Now you want to disable the 000-default.conf file
sudo a2dissite 000-default.conf
Once you have completed the above steps and reloaded Apache using sudo service apache2 reload you should be able to visit the site on it’s IP and see the content of the file you have made on /var/www/localhost/public_html/index.html. Likewise, you should be able to visit the individual sites you have made using their URL.
Enable or disable Apache moduels
Apache comes with a bunch of modules that just need to be enabled. To see available modules type this command
ls /etc/apache2/mods-available
To see what is already enabled type this
ls /etc/apache2/mods-enabled
We need the rewrite module so let’s go on and enable that
sudo a2enmod rewrite
Once completed restart Apache
sudo service apache2 restart
Likewise, you can disable a module. Remember to restart Apache for the change to become effective
udo a2dismod mod_name
Chroot users and enable SFTP
If you try to use SSH to access the site you should be denied. That’s good. Now it is time to set up SFTP so it works with suPHP and gives the files you upload the correct permissions.
SFTP is a secure way of transferring your files to and from your server. It should work out of the box but you need to change a some things to get the right file permissions when using suPHP. Open the sshd_config file
sudo nano /etc/ssh/sshd_config
…and find the line
Subsystem sftp /usr/libexec/openssh/sftp-server
Change the line so i reads
Subsystem sftp internal-sftp
…and finally add this block to the end of the file (where sftp is the name of the group that restricts the users to SFTP access)
Match group sftp ChrootDirectory %h X11Forwarding no AllowTcpForwarding no ForceCommand internal-sftp -u 22
Save the file and reload the SSH service to
sudo service ssh reload
After modifying OpenSSH we need to modify our chrooted web user accounts to have the sftp group as a secondary group. But first, we need to add that group
sudo addgroup --system sftp
Modify the web user accounts to have the sftp group as a secondary group
sudo usermod -G sftp localhost
If the chroot environment is in a user’s home directory both /var/www and /var/www/username must be owned by root and should have permissions along the lines of 755 or 750.
To change this execute the this
sudo chown root:root /var/www/localhost
…and this
sudo chmod 755 /var/www/localhost
These users will now be unable to create files anywhere in the system but in subfolders to /var/www/username, since these directory are now owned by the root user.
Just to make sure that the correct owner and group is set for the files and folders below the user run this query
sudo chown localhost:localhost /var/www/localhost/*
This will make sure that the user is able to upload to all folders below /var/www/username/ – in our case the folders logs and not least public_html
You should now be all set and be able to upload/download using SFTP.
You might need to disconnect your SFTP connection and reconnect to make the right file permission come through.
SSH and SFTP on different ports
Our SSH access is is behind a firewall and so we need to connect to a VPN before we can access our servers via SSH. However, we need to be able to give users outside our organisation – who don’t have access to connect to our VPN – access to use SFTP. To solve this, we decided to run two instances of SSH with different configuration.
1. First step is to make a copy of the contents of the /etc/ssh/ directory. We have chosen to call the copy ssh-sftp throughout this example.
sudo cp -r /etc/ssh /etc/ssh-sftp
Open the sshd_config file en the newly duplicated folder.
sudo nano /etc/ssh-sftp/sshd_config
Change the port to something high that is not on this list of know port search numbers. In our example we change it to port 25468. You will also need to add a reference to PidFile so the changes in the sshd_config looks like this.
Port 25468 PidFile /var/run/ssh-sftp.pid
While you have that file open you also want to allow only the group sftp. This is done by entering the following just below the above.
AllowGroups sftp
Lastly, append the following lines to the files if they are not already there (from a previous step)
Match group sftp ChrootDirectory %h X11Forwarding no AllowTcpForwarding no ForceCommand internal-sftp -u 22
Save the file.
2. (This step might be obsolete) Make a copy of the /etc/default/ssh file and name the copy ssh-sftp.
sudo cp /etc/default/ssh /etc/default/ssh-sftp
Open the file…
sudo nano /etc/default/ssh-sftp
…and append ‘-f /etc/ssh-sftp/sshd_config’ to the SSHD_OPTS so the file looks like this.
# Default settings for openssh-server. This file is sourced by /bin/sh from # /etc/init.d/ssh. # Options to pass to sshd SSHD_OPTS='-f /etc/ssh-sftp/sshd_config'
Save the file.
3. Copy the /etc/init/ssh.conf file and name the copy ssh-sftp.conf
sudo cp /etc/init/ssh.conf /etc/init/ssh-sftp.conf
Open the file…
sudo nano /etc/init/ssh-sftp.conf
…and change all instances of ssh with ssh-sftp. Furthermore, comment the line that says exec /usr/sbin/sshd -D and replace it with exec /usr/sbin/sshd-sftp -D -f /etc/ssh-sftp/sshd_config so the file looks like this (first part of the file is omitted).
#___CODE ABOVE OMITTED___ pre-start script test -x /usr/sbin/sshd || { stop; exit 0; } test -e /etc/ssh-sftp/sshd_not_to_be_run && { stop; exit 0; } mkdir -p -m0755 /var/run/sshd end script # if you used to set SSHD_OPTS in /etc/default/ssh, you can change the # 'exec' line here instead #exec /usr/sbin/sshd -D exec /usr/sbin/sshd-sftp -D -f /etc/ssh-sftp/sshd_config
Save the file.
4. Make a symbolic link from /usr/sbin/sshd-sftp to /user/sbin/sshd by executing this command.
sudo ln -s /usr/sbin/sshd /usr/sbin/sshd-sftp
5. Copy the file /etc/init.d/ssh and name it ssh-sftp.
sudo cp /etc/init.d/ssh /etc/init.d/ssh-sftp
Open the file…
sudo nano /etc/init.d/ssh-sftp
…and replace all sshd with sshd-sftp as well as all ssh with ssh-sftp. The file should look somewhat similar to this.
#! /bin/sh ### BEGIN INIT INFO # Provides: sshd-sftp # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: OpenBSD Secure Shell server ### END INIT INFO set -e # /etc/init.d/ssh-sftp: start and stop the OpenBSD "secure shell(tm)" daemon test -x /usr/sbin/sshd-sftp || exit 0 ( /usr/sbin/sshd-sftp -? 2>&1 | grep -q OpenSSH ) 2>/dev/null || exit 0 umask 022 if test -f /etc/default/ssh-sftp; then . /etc/default/ssh-sftp fi . /lib/lsb/init-functions if [ -n "$2" ]; then SSHD_OPTS="$SSHD_OPTS $2" fi # Are we running from init? run_by_init() { ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ] } check_for_upstart() { if init_is_upstart; then exit $1 fi } check_for_no_start() { # forget it if we're trying to start, and /etc/ssh-sftp/sshd_not_to_be_run exists if [ -e /etc/ssh-sftp/sshd_not_to_be_run ]; then if [ "$1" = log_end_msg ]; then log_end_msg 0 || true fi if ! run_by_init; then log_action_msg "OpenBSD Secure Shell server not in use (/etc/ssh-sftp/sshd_not_to_be_run)" || true fi exit 0 fi } check_dev_null() { if [ ! -c /dev/null ]; then if [ "$1" = log_end_msg ]; then log_end_msg 1 || true fi if ! run_by_init; then log_action_msg "/dev/null is not a character device!" || true fi exit 1 fi } check_privsep_dir() { # Create the PrivSep empty dir if necessary if [ ! -d /var/run/sshd-sftp ]; then mkdir /var/run/sshd-sftp chmod 0755 /var/run/sshd-sftp fi } check_config() { if [ ! -e /etc/ssh-sftp/sshd_not_to_be_run ]; then /usr/sbin/sshd-sftp $SSHD_OPTS -t || exit 1 fi } export PATH="${PATH:+$PATH:}/usr/sbin:/sbin" case "$1" in start) check_for_upstart 1 check_privsep_dir check_for_no_start check_dev_null log_daemon_msg "Starting OpenBSD Secure Shell server" "sshd-sftp" || true if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd-sftp.pid --exec /usr/sbin/sshd-sftp -- $SSHD_OPTS; then log_end_msg 0 || true else log_end_msg 1 || true fi ;; stop) check_for_upstart 0 log_daemon_msg "Stopping OpenBSD Secure Shell server" "sshd-sftp" || true if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/sshd-sftp.pid; then log_end_msg 0 || true else log_end_msg 1 || true fi ;; reload|force-reload) check_for_upstart 1 check_for_no_start check_config log_daemon_msg "Reloading OpenBSD Secure Shell server's configuration" "sshd-sftp" || true if start-stop-daemon --stop --signal 1 --quiet --oknodo --pidfile /var/run/sshd-sftp.pid --exec /usr/sbin/sshd; then log_end_msg 0 || true else log_end_msg 1 || true fi ;; restart) check_for_upstart 1 check_privsep_dir check_config log_daemon_msg "Restarting OpenBSD Secure Shell server" "sshd-sftp" || true start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/sshd-sftp.pid check_for_no_start log_end_msg check_dev_null log_end_msg if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd-sftp.pid --exec /usr/sbin/sshd-sftp -- $SSHD_OPTS; then log_end_msg 0 || true else log_end_msg 1 || true fi ;; try-restart) check_for_upstart 1 check_privsep_dir check_config log_daemon_msg "Restarting OpenBSD Secure Shell server" "sshd-sftp" || true RET=0 start-stop-daemon --stop --quiet --retry 30 --pidfile /var/run/sshd-sftp.pid || RET="$?" case $RET in 0) # old daemon stopped check_for_no_start log_end_msg check_dev_null log_end_msg if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd-sftp.pid --exec /usr/sbin/sshd-sftp -- $SSHD_OPTS; then log_end_msg 0 || true else log_end_msg 1 || true fi ;; 1) # daemon not running log_progress_msg "(not running)" || true log_end_msg 0 || true ;; *) # failed to stop log_progress_msg "(failed to stop)" || true log_end_msg 1 || true ;; esac ;; status) check_for_upstart 1 status_of_proc -p /var/run/sshd-sftp.pid /usr/sbin/sshd-sftp sshd-sftp && exit 0 || exit $? ;; *) log_action_msg "Usage: /etc/init.d/ssh-sftp {start|stop|reload|force-reload|restart|try-restart|status}" || true exit 1 esac exit 0
6. Start the new ssh-sftp service
sudo service ssh-sftp start
You should now be able to use SFTP externally on port 25468 with your users that are a member of the sftp group (remember to open that port in the firewall if you have one). These users will not be able to get shell access but only have SFTP access.
Install PHP
We are now going to install PHP
sudo apt-get install libapache2-mod-php5
This will install and enable PHP as an Apache module.
You now might want to install additional PHP modules. In this case we are going to install MySQL and cURL
sudo apt-get install php5-mysql php5-curl
Restart Apache
sudo service apache2 restart
You should now be able to execute PHP scripts in your /var/www/[user]/public_html directories. To test, add a file named phpinfo.php to /var/www/localhost/public_html
sudo nano /var/www/localhost/public_html/phpinfo.php
…and enter the following in the file and save it
<?php phpinfo(); ?>
Visit the site on its IP or the domain mapped to localhost and type /phpinfo.php after the IP/domain. You should now get some info about your system.
You should remove this file once you are done testing. This can be done by executing
sudo rm /var/www/localhost/public_html/phpinfo.php
Install and set up suPHP
If you wish to run different sites under different users, suPHP is the answer. Furthermore, it gives an additional layer of security.
To install and enable suPHP execute the following
sudo apt-get install suphp-common libapache2-mod-suphp
Executing a PHP file now will return a 500 error. Don’t worry; we will fix this below.
Open each of your virtual host files. I’ll start with 000-localhost.conf
sudo nano /etc/apache2/sites-available/000-localhost.conf
In each host file add the following lines
php_admin_flag engine off suPHP_Engine on
…so the file will look like this
<VirtualHost *:80> #ServerName localhost #ServerAlias localhost ServerAdmin webmaster@example.com DocumentRoot /var/www/localhost/public_html php_admin_flag engine off suPHP_Engine on <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/localhost/public_html> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog /var/www/localhost/logs/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog /var/www/localhost/logs/access.log combined </VirtualHost>
Do the same for your other virtual host files
Edit the suPHP cofing file
sudo nano /etc/suphp/suphp.conf
Where it says
umask=0077
change it to
umask=0022
While you are there you might also want to change the loglevel from info to warn so it looks like this. Otherwise your suPHP log file will be flooded very quickly.
loglevel=warn
The full file should look something like this (I have uncommented the lines instead of replacing them with the above)
[global] ;Path to logfile logfile=/var/log/suphp/suphp.log ;Loglevel loglevel=warn ;User Apache is running as webserver_user=www-data ;Path all scripts have to be in docroot=/var/www:${HOME}/public_html ;Path to chroot() to before executing script ;chroot=/mychroot ; Security options allow_file_group_writeable=false allow_file_others_writeable=false allow_directory_group_writeable=false allow_directory_others_writeable=false ;Check wheter script is within DOCUMENT_ROOT check_vhost_docroot=true ;Send minor error messages to browser errors_to_browser=false ;PATH environment variable env_path="/bin:/usr/bin" ;Umask to set, specify in octal notation ;umask=0077 umask=0022 ; Minimum UID min_uid=100 ; Minimum GID min_gid=100 [handlers] ;Handler for php-scripts application/x-httpd-suphp="php:/usr/bin/php-cgi" #application/x-httpd-php="php:/usr/bin/php-cgi" ;Handler for CGI-scripts x-suphp-cgi="execute:!self"
You should now be able to execute PHP files again. But now Apache should use the respective user for the execution. To check it out create the following file
sudo nano /var/www/localhost/public_html/whoami.php
Enter the following in the file and save it
<?php echo exec('/usr/bin/whoami'); ?>
If you visit the site with your IP or domain and whoami.php it should write “localhost”
If you still get a 500 error there can be several reasons.
Check your file permissions. suPHP requires them to be atmost 755. Execute the following to correct the permissions
sudo chmod -Rv og-w /var/www/localhost/public_html/
Also, make sure you have set umask 0022 in
sudo nano /var/www/localhost/.bashrc
You could also have user id issues. This can be fixed by running
sudo chown -R localhost:localhost /var/www/localhost/public_html/
If you have PHP OPCode caching disable it and restart your server
If you are still experiencing issues check the Apache error log at
nano /var/www/localhost/logs/error.log
You might also want to check the suPHP log at
sudo nano /var/log/suphp/suphp.log
You might also want to change the default upload limit on 2048KiB. To do this edit the php.ini file. (if you cannot find the loaded php.ini file look in the phpinfo.php file.)
sudo nano /etc/php5/cgi/php.ini
Find an change the following lines to the desired size
; Maximum allowed size for uploaded files. upload_max_filesize = 128M ; Must be greater than or equal to upload_max_filesize post_max_size = 128M
Install MySQL
This is pretty straight forward. Just run
sudo apt-get install mysql-server
Then run the installation scripts…
sudo mysql_install_db
…and…
sudo mysql_secure_installation
When prompted make sure to set a secure root password. You can safely answer yes to all the questions.
Install phpMyAdmin
Even though phpMyAdmin is in the Ubuntu repository we are going to download it from source and upload it manually to the server.
Go to the phpMyAdmin downloads page and download the latest version of phpMyAdmin
Once downloaded unpack it and upload the folder (which in this example is named phpmyadmin) to your public_html using your localhost user (created in a previous step)
You should now be able to access phpMyAdmin at either the IP followed by /phpmyadmin or any domain assigned to the 000-localhost.conf (in /etc/apache2/sites-available/000-localhost.conf) followed by /phpmyadmin.
Once you confirmed it working change the url to something else so it is more difficult to guess for bots.
sudo mv /var/www/localhost/public_html/phpmyadmin /var/www/localhost/public_html/my-new-phpmyadmin-name
Instead of accessing phpMyAdmin on /phpmyadmin you should now access it on /my-new-phpmyadmin-name.
Finally you probably want to make access further secure – e.g. limiting access by IP or a second password. There is a great tutorial on securing phpMyAdmin on Digital Ocean (which is, by the way, a great hosting company)
Send emails with SMTP
For sending emails there is an excellent transactional email service called Mandrill by the folks at MailChimp. It is very simple to use and we only need to install SSMTP as a gateway.
sudo apt-get install ssmtp
Once installed open SSMTP settings…
sudo nano /etc/ssmtp/ssmtp.conf
…and make the content look like this
# ---- basic config root=your@email.com UseTLS=YES UseSTARTTLS=YES hostname=fqdn_of_your_server.example.com FromLineOverride=NO # ---- mandrill config mailhub=smtp.mandrillapp.com:587 AuthUser=your_mandrill_email@example.com AuthPass=mandrill_api_key
Run CRON jobs and send emails with result
Now that we are able to send emails we should be able to mail the results for CRON jobs. To make a CRON job for a specific user (in this case the user named sub-one.example.com) run the crontab command
sudo crontab -e -u sub-one.example.com
Append the following lines
MAILTO=your@email.com 5 20 7,16,26 * * php /var/www/sub-one.example.com/public_html/my-job-to-execute.php
This will execute the my-job-to-execute.php file at five minutes past 8 p.m. every 7th, 16th and 26th of every month and email the result to the designated email.
If you are having issues or suggestions for improvements don’t hesitate to make a comment.
Resources
http://www.intechgrity.com/install-apache-with-virtual-host-concept-on-ubuntu-vps/
https://bensmann.no/restrict-sftp-users-to-home-folder/
http://sysadmin.circularvale.com/server-config/setting-a-umask-for-chrooted-sftp-users/
https://stackoverflow.com/questions/3889377/set-umask-for-a-sftp-account
http://ubuntuforums.org/showthread.php?t=1497376
http://www.unixlore.net/articles/five-minutes-to-even-more-secure-ssh.html