Web-server – Apache2 main domain redirecting to sub-domain when port is used on main domain

apache-2.4virtualhostweb-server

All of my http > https redirects are working perfectly fine.

I have setup a separate sub-domain and port to use with PHPMyAdmin to access a MySQL DB so it is less likely to get be scanned by bots etc. The examples below are very demonstration purposes only.

The problem I have is if I enter the port used on the PHPMyAdmin sub-domain after the main domain without https prefixed so that it becomes http://example.com:8080, after what starts off as a timing out webpage, it eventually redirects me to the https site but on the sub-domain at https://phpmyadmin.example.com:8081.

How do I stop the main domain redirecting to the sub-domain and instead timeout as a typical server would? If someone was to guess every port at the main domain someone would eventually find the sub-domain.

/etc/apache2/sites-enabled/example.com

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin webmaster@example.com
    Redirect permanent / https://example.com
    DocumentRoot /var/www/example.com
    # <Directory />
    #     DirectoryIndex index.html index.php
    #     Require all denied
    #     Options FollowSymLinks
    #     AllowOverride All
    # </Directory>
    #ErrorLog ${APACHE_LOG_DIR}/error.log
    #CustomLog ${APACHE_LOG_DIR}/access.log combined    
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/example.com
    # <Directory />
    #     DirectoryIndex index.html index.php
    #     Require all denied
    #     Options FollowSymLinks
    #     AllowOverride All
    # </Directory>
    #ErrorLog ${APACHE_LOG_DIR}/error.log
    #CustomLog ${APACHE_LOG_DIR}/access.log combined
    SSLEngine On
    SSLCertificateFile "/etc/ssl/certs/example.com.crt"
    SSLCertificateKeyFile "/etc/ssl/private/example.com.key"
    SSLCertificateChainFile "/etc/ssl/certs/example.com.ca-bundle"
</VirtualHost>


/etc/apache2/sites-enabled/phpmyadmin.example.com

<VirtualHost *:8080>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    Redirect permanent / https://phpmyadmin.example.com:8081
</VirtualHost>

<VirtualHost *:8081>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    DocumentRoot /usr/share/phpmyadmin
#    RewriteEngine On
#    RewriteCond %{HTTPS} off
#    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
    <Directory />
        Require all denied
        Options FollowSymLinks
        AllowOverride All
    </Directory>
    LogLevel notice
    CustomLog /var/log/apache2/access.log combined
    ErrorLog /var/log/apache2/error.log
    Include /etc/phpmyadmin/apache.conf
    SSLEngine On
    SSLCertificateFile "/etc/letsencrypt/live/phpmyadmin.example.com/cert.pem"
    SSLCertificateKeyFile "/etc/letsencrypt/live/phpmyadmin.example.com/privkey.pem"
    SSLCertificateChainFile "/etc/letsencrypt/live/phpmyadmin.example.com/chain.pem"
</VirtualHost>

UPDATE 1

I think the problem is between the http to https redirect in general. By typing the HTTP port for the sub-domain onto the main domain instead, Apache's VirtualHost sees that as the initiator to redirect the http to https but totally ignores the domain is supposed to do it on. Is there a way I can isolate http to https redirect dependant on what domain prefixes it?

UPDATE 2

Are there any rewrite rules/conditions I could use on each of the http virtual hosts to only respond to the port it is listening on? My knowledge of rewrite rules etc are non-existent so I'm relying on good Google research skills to find me the write websites. I have stumbled upon this.

RewriteEngine On
RewriteCond %{HTTP_HOST} ^yourdomain\.com [NC]
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://www.yourdomain.com/$1 [R,L]

UPDATE 3

I've fixed the issue.

However, if I was to access the example URL http://example.com:8080 I get a 403 forbidden error message

Forbidden

You don't have permission to access this resource.

How would I get Apache to timeout the connection rather than just flat out refuse it?

The two VirtualHost files now become:

/etc/apache2/sites-enabled/example.com

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/example.com
    RewriteEngine On
    RewriteCond %{HTTP_HOST} example.com [NC]
    RewriteCond %{SERVER_PORT} 80
    RewriteRule ^(.*)$ https://www.example.com$1 [R,L]
    <Directory />
        DirectoryIndex index.html index.php
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined    
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin webmaster@example.com
    DocumentRoot /var/www/example.com
    <Directory />
    DirectoryIndex index.html index.php
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    SSLEngine On
    SSLCertificateFile "/etc/ssl/certs/example.com.crt"
    SSLCertificateKeyFile "/etc/ssl/private/example.com.key"
    SSLCertificateChainFile "/etc/ssl/certs/example.com.ca-bundle"
</VirtualHost>

/etc/apache2/sites-enabled/phpmyadmin.example.com

<VirtualHost *:8080>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    RewriteEngine On
    RewriteCond %{HTTP_HOST} phpmyadmin.example.com [NC]
    RewriteCond %{SERVER_PORT} 8080
    RewriteRule ^(.*)$ https://phpmyadmin.example.com:8081$1 [R,L]
</VirtualHost>

<VirtualHost *:8081>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    DocumentRoot /usr/share/phpmyadmin
    <Directory />
        DirectoryIndex index.html index.php
    </Directory>
    LogLevel notice
    CustomLog /var/log/apache2/access.log combined
    ErrorLog /var/log/apache2/error.log
    Include /etc/phpmyadmin/apache.conf
    SSLEngine On
    SSLCertificateFile "/etc/letsencrypt/live/phpmyadmin.example.com/cert.pem"
    SSLCertificateKeyFile "/etc/letsencrypt/live/phpmyadmin.example.com/privkey.pem"
    SSLCertificateChainFile "/etc/letsencrypt/live/phpmyadmin.example.com/chain.pem"
</VirtualHost>

Best Answer

personally, I would favour a solution using either a firewall, or ssh port forwarding and localhost, to expose the phpmyadmin service in a secure manner to trusted hosts, and definitely use decent authentication rather than "obscurity security". However to try to answer the question at hand...

Because you only have one VirtualHost on each port, the first one defined becomes the default for requests to that port, regardless of whatever domain name is set in ServerName and ServerAlias.

<VirtualHost *:8080>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    Redirect permanent / https://phpmyadmin.example.com:8081/
</VirtualHost>

This is going to match any request to *:8080, including:

and redirect them to the service on port 8081.

In order to differentiate the requests, I would add a second VirtualHost on port 8080 which catches everything else (including http://example.com:8080), and redirects it somewhere non-existing which would cause a timeout.

<VirtualHost *:8080>
    ServerName example.com
    # redirect to somewhere non-existing will cause timeout
    Redirect permanent / https://192.168.99.99/
</VirtualHost>

<VirtualHost *:8080>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    Redirect permanent / https://phpmyadmin.example.com:8081/
</VirtualHost>

(note that the default one needs to come first)

Note:

How do I stop the main domain redirecting to the sub-domain and instead timeout as a typical server would?

Unfortunately, by the time apache has received the request and is processing it, the point at which it can tell whether the request is to http://phpadmin.example.com:8080 or http://example.com:8080, it is too late for apache itself to drop the initial connection and cause a timeout.

I would generally use a firewall for this purpose, either directly in iptables, or ufw/firewalld depending on your distribution, to limit access to port 8080 to only known trusted hosts.

Update: using mod_security

It looks like it might be possible to use the mod_security module to simulate the timeout: https://serverfault.com/a/401592/47650

so something like this would send a FIN packet to drop the connection after the request header was processed...

<VirtualHost *:8080>
  ServerName example.com
  SecRuleEngine On
  SecAction id:1,phase:1,nolog,drop
</VirtualHost>

<VirtualHost *:8080>
    ServerName phpmyadmin.example.com
    ServerAlias phpmyadmin.example.com
    Redirect permanent / https://phpmyadmin.example.com:8081/
</VirtualHost>

https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-%28v2.x%29#drop

Related Topic