I have a couple of sites that I am keeping on a VPS with the above configuration. In this article, I will present you all the steps and the configurations you need to make to have
Virtualmin is a great tool and is offering all you need from DNS, email, FTP, and server administration. It has a GUI interface from where you can manage everything easily and is very stable.
The first thing to do is to have your VPS ready and installed. I am using DigitalOcean for my sites as it has exactly what I need. You can check the review here: DigitalOcean Review
For a more affordable option to DigitalOcean, you can check this Hetzner Review.
To do this you login into
The size of the VPS is up to you but to have a couple of small sites hosted the 5$ plan can be enough ( without a lot of email processing) Also you can increase your size in the future if you need more.
After the creation process finishes you should receive an email with your VPS IP with the user name and password. The next thing you need to download Putty so you can connect to the VPS and run the commands.
sudo fallocate -l 1G /swapfile
sudo dd if=/dev/zero of=/swapfile bs=1024 count=1048576
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab
After you add the swap you can upgrade your machine to the latest version. to do that you just run:
yum update
yum upgrade
After you finish with the above you just reboot the machine and you are set to install Virtualmin
The install is very easy, you just need to install wget first so you can take the package and after just run the install with /root/install.sh -b LEMP
yum install wget
wget http://software.virtualmin.com/gpl/scripts/install.sh
sh /root/install.sh -b LEMP
▣▣▣ Phase 3 of 3: Configuration
[1/23] Configuring AWStats [ ✔ ]
[2/23] Configuring Bind [ ✔ ]
[3/23] Configuring ClamAV [ ✔ ]
[4/23] Configuring Dovecot [ ✔ ]
[5/23] Configuring Firewalld [ ✔ ]
[6/23] Configuring MySQL [ ✔ ]
[7/23] Configuring NTP [ ✔ ]
[8/23] Configuring Net [ ✔ ]
[9/23] Configuring Nginx [ ✔ ]
[10/23] Configuring ProFTPd [ ✔ ]
[11/23] Configuring Procmail [ ✔ ]
[12/23] Configuring Quotas
The filesystem / could not be remounted with quotas enabled.
You may need to reboot your system, and/or enable quotas in the Disk
Quotas module. [ ⚠ ]
[13/23] Configuring SASL [ ✔ ]
[14/23] Configuring Shells [ ✔ ]
[15/23] Configuring SpamAssassin [ ✔ ]
[16/23] Configuring Status [ ✔ ]
[17/23] Configuring Upgrade [ ✔ ]
[18/23] Configuring Usermin [ ✔ ]
[19/23] Configuring Webalizer [ ✔ ]
[20/23] Configuring Webmin [ ✔ ]
[21/23] Configuring Fail2banFirewalld [ ✔ ]
[22/23] Configuring Postfix [ ✔ ]
[23/23] Configuring Virtualmin [ ✔ ]
▣▣▣ Cleaning up
[SUCCESS] Installation Complete!
[SUCCESS] If there were no errors above, Virtualmin should be ready
[SUCCESS] to configure at https://ns1.wpdoze.site:10000 (or https://159.65.108.159
10.46.0.5:10000).
After 10 minutes or so all the packages will be installed and you are ready to configure the installation.
https://159.65.108.159:10000/
After you need to access the UI with the server public
After all this, you are set you just need to point your domain name to the DNS and create the custom DNS records.
Now Virtualmin is installed and ready to host sites 🙂 as next actions, you need to go where you host your domain and create the custom DNS names pointing to the server IP. I am using NameCheap for this. And you need to go under the domain name and hit Manage -> Advanced DNS and add the ns1 and ns2 with the IP as below:
Now you have your custom nameserver set up and you just need to point your domain to use them from Domain Tab like below:
In this area, we will do some extra configurations for multiple PHP versions, create a custom server template that will deploy WordPress automatically, and do some Nginx configurations for advanced performance.
Now as we have everything set up we can do some extra configs to have multiple PHP versions on our site, by default Virtualmin is installing PHP 5.6 with 7.0 you need to run the below to have multiple versions of PHP 7:
yum install centos-release-scl
yum install rh-php71 rh-php71-php-mysqlnd
yum install rh-php72 rh-php72-php-mysqlnd
Next, I want to have a special server template and have some customization done for WordPress like installing it automatically when I add a domain and having the user and DB already added. Below are the steps to have this done:
For this I will use a directory under /etc/wordpress, you can run the below commands to have everything set up:
# cd /etc/
# mkdir wordpress
# cd wordpress/
# mkdir public_html
# cd public_html/
# wget https://wordpress.org/latest.zip
# unzip latest.zip
# mv wordpress/* .
# rm -rf wordpress
#cp wp-config-sample.php wp-config.php
define( 'DB_NAME', '${USER}' );
/** MySQL database username */
define( 'DB_USER', '${USER}' );
/** MySQL database password */
define( 'DB_PASSWORD', '${PASS}' );
Now you need to create the template to have it configured to do so go to Virtualmin > System Settings > Server Templates and hit create a template from the default settings:
Then give it name like WordPress and hit Create and Next. Now in the Home Directory use these configurations:
Then hit Save
With this configuration, while choosing a WordPress template you will have by default WordPress installed.
Next to that, we want to do is to add some nginx configurations to have a better performance. All that is needed to be done is to add below in the /etc/nginx/nginx.conf. This can be done in the Virtualmin UI from the region: Webmin – Servers- Nginx
Next, you need to add the below code:
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
##
# Gzip Settings
##
gzip on;
gzip_comp_level 9;
gzip_proxied expired no-cache no-store private auth;
gzip_min_length 500;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
~image/ max;
}
The configuration will look like this:
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
##
# Gzip Settings
##
gzip on;
gzip_comp_level 9;
gzip_proxied expired no-cache no-store private auth;
gzip_min_length 500;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
~image/ max;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
server_names_hash_bucket_size 128;
}
Now you just need to hit save
We have everything we need to have our first website added into Virtualmin, until now we have:
The configuration will run and Virtualmin will add everything needed to have your first site. The site will be with SSL but it will have a self-signed one. If you visit the link you will see an exception, next we will need to add a LetsEncrypt SSL and make some Nginx configs:
All websites should have an SSL certificate and LetsEncypt helps you in generating a free one for you. You also need to renew it in 3 by 3 months. Virtualmin helps you generate the certificate automatically very easily and handle also the renewal. To have the certificate you go to Virtualmin – Server Configuration – SSL Certificate > Let’s
Now you have the website with WordPress and the Let’s Encrypt certificate up and running. Next, you access the site URL with HTTPS and go thru the WordPress Installation process.
To have all the traffic redirected from
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
Add support for WordPress permalinks:
location / {
try_files $uri $uri/ /index.php?$args;
}
Add HTTP2:
Nginx support http2 for faster loading of the websites under HTTPS, to enable this you just need to add into location the http2 like below
listen 159.203.100.92:443 default ssl http2;
Then Save and Close and Apply Nginx Config
Then your server will look like this:
server {
server_name wpdoze.site www.wpdoze.site;
listen 159.203.100.92;
root /home/wpdoze/public_html;
index index.html index.htm index.php;
access_log /var/log/virtualmin/wpdoze.site_access_log;
error_log /var/log/virtualmin/wpdoze.site_error_log;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME /home/wpdoze/public_html$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT /home/wpdoze/public_html;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param HTTPS $https;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/php-nginx/155437756624142.sock/socket;
}
listen 159.203.100.92:443 default ssl http2;
ssl_certificate /home/wpdoze/ssl.combined;
ssl_certificate_key /home/wpdoze/ssl.key;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
Julius says:
Dragos,
Sorry to bother you. I found the problem.
My fault..
Thanks,
Julius
Julius says:
Hello Dragos,
So i set up a CentOS 8 server and followed the instructons here as usual but this time I am getting this error:
“Failed to save configuration file : Configuration is invalid : nginx: [emerg] “map” directive is not allowed here in /etc/nginx/nginx.conf:60 nginx: configuration file /etc/nginx/nginx.conf test failed”
I am doing some thing wrong or is the directive not supported on CentOS 8?
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
~image/ max;
}
Thanks for your help!
Julius
MacGee says:
Thank you very much for this excellent tutorial. Please, can you kindly help with answers to these questions?
1. As I watched your video tutorial, I realized you were able to login to webmin with the hostname: https://ns1.wpdoze.site:100000.
What is the configuration in getting the hostname (server.example.com) as webmin login and not the ip address like: https://12.122.23.5:10000 (not my real ip address though).
Note: I have setup my hostname (server.example.com) and everything is working fine except that I can’t login with the hostname.
I have also setup the DNS (or A records) and have connected it to a Cloudflare account, and the two sites I have setup are all working perfectly.
2. I was successful with getting a Letsencrypt for my hostname (server.example.com) but unfortunately, because the hostname is not resolvable, I can’t get it to work on the hostname.
I have done all the things I have learnt from online tutorials so that, in the mean time, I can even use the Letsencrypt to secure my webmin ip address login like this: https://12.122.23.5:10000 but still receives a browser warning. Is there a better to get this fixed? Kindly let have your email address.
3. Playing media files on my wordpress site gives this error: Media error: Format(s) not supported or source(s) not found Download File: http://stream.zenolive.com/km4bq88rhv5tv.
I have tried to diable the “Always Use HTTPS” in my Cloudflare account but all to no avail.
Please, could this error not be because of my nginx.conf file settings pasted below:
user nginx;
# Replace worker_process value with the # of CPUs you have
worker_processes 1;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_header_timeout 12;
client_body_timeout 12;
client_max_body_size 8m;
client_header_buffer_size 1k;
client_body_buffer_size 16k;
large_client_header_buffers 2 2k;
send_timeout 10;
keepalive_timeout 65;
reset_timedout_connection on;
server_names_hash_max_size 1024;
server_names_hash_bucket_size 1024;
server_tokens off;
add_header X-XSS-Protection “1; mode=block”;
add_header X-Frame-Options “SAMEORIGIN”;
ignore_invalid_headers on;
connection_pool_size 112;
request_pool_size 2k;
output_buffers 2 8k;
postpone_output 1460;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging Settings
log_format gzip ‘$remote_addr – $remote_user [$time_local] ‘
‘”$request” $status $bytes_sent ‘
‘”$http_referer” “$http_user_agent” “$gzip_ratio”‘;
access_log /var/log/nginx/access.log;
access_log off;
# Compression gzip
gzip on;
gzip_vary on;
gzip_disable “MSIE [1-6]\.(?!.*SV1)”;
gzip_proxied expired no-cache no-store private auth;
gzip_min_length 500;
gzip_comp_level 6;
gzip_buffers 4 32k;
gzip_types text/plain text/xml text/css text/js application/x-javascript application/xml image/png image/x-icon image/gif image/jpeg image/svg+xml application/xml+rss text/javascript application/atom+xml application/javascript application/json application/x-font-ttf font/opentype;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
open_file_cache max=200000 inactive=20s;
open_log_file_cache max=1024 inactive=30s min_uses=2;
# SSL Settings
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# Cache bypass
map $http_cookie $no_cache {
default 0;
~SESS 1;
~wordpress_logged_in 1;
}
Many thanks and patiently waiting for your kind response
Julius says:
Thank you so much for these tutorials. Please can you do a tutorial on how to upgrade to Nginx to 1.14 and MariaDB 5.x to MariaDB 10.3?
Thank you again!
admin says:
Hello,
Sure. I can do that.
Thanks,
Dragos
Julius says:
Hey Dragos,
Thanks, man! I was able to get a new server setup with latest Nginx, PHP7.2 and MariaDB10.3. I don’t know if vhost will work fine but we’ll let you know.
1 question: in the wp-config modification – what value can I use if I want to use the actual database name instead of ${USER} in line “define( ‘DB_NAME’, ‘database_name_here’ );”?
Thanks again for your help!
Julius
admin says:
Hello Julius,
For question 1 when you create an account with the default settings Virtualmin will create a database with the user name, that’s why you have the username there. You can add the database name also if you want with ${DB} you have the list here: https://www.virtualmin.com/documentation/system/template_variables
If this is not working you can modify manually the file.
For php.ini you can check the GUI in Services – PHP 7.0 Configs and there you have the path and option to edit the file from UI.
Julius says:
Thank you, Dragos! You’re amazing, man! I hope more people find out about you skills on Virtualmin. It’s a powerful system but is very confusing for most new users (especially those coming from CPanel/WHM).
BTW, have you successfully installed SuiteCRM on Virtualmin/Nginx?
admin says:
Thanks Julius. I have not tried but if SuiteCRM is a web app and it works on nginx it should work. The documentation should be followed on how to configure NGINX.