I am trying to figure out a mind-numbing apache issue where requests are going through two layers of apache reverse proxies before hitting upstream services. Most traffic seems to make it through fine. The notable exception is websockets.
In particular, this is a test request
curl -i -H 'Connection: Upgrade' -H 'Upgrade: websocket' localhost:80/test.html
When proxying requests from port 80 to port 8080, I notice (using tcpdump and Wireshark) that the Upgrade header has been removed and Connection: Keep-Alive has been set instead. Moreover, any attempts I have made at resetting the Connection header to Upgrade and Upgrade: websocket have been impotent.
Note that the upstream service needs Connection: Upgrade and Upgrade: websocket to initiate the websocket (I get a 404 error without those headers).
Why is Apache forcing Connection: Keep-Alive when proxying to itself? Is there any way to force it to pass along Connection / Upgrade headers or set those values manually? RequestHeader and friends have unfortunately not been helpful. Without Connection and Upgrade making it through the reverse proxy, the upstream service pukes and throws a 404/Not found at the websocket endpoint.
ServerRoot "/usr/local/apache2"
Listen 80
Listen 8080
LogLevel rewrite:trace8
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule auth_mellon_module modules/mod_auth_mellon.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule ssl_module modules/mod_ssl.so
<IfModule unixd_module>
User daemon
Group daemon
</IfModule>
<VirtualHost *:80>
LogLevel rewrite:trace8
ServerName localhost
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket
RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket
RewriteRule /(.*) http://localhost:8080/$1 [P,L]
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
ProxyRequests Off
</VirtualHost>
<VirtualHost *:8080>
LogLevel rewrite:trace8
ServerName localhost
UseCanonicalName On
RewriteEngine On
ProxyPass / http://proxy-debug:8080/
ProxyPassReverse / http://proxy-debug:8080/
ProxyRequests Off
</VirtualHost>
ServerAdmin you#example.com
ErrorLog /proc/self/fd/2
DocumentRoot "/"
I do not completely understand how this works, but the nuggets of wisdom I have gleaned and the solution I have devised are as follows:
Apache does not understand websockets in one sense (i.e. when it is talking to itself). As a result, you need to use mod_rewrite and set the protocol to ws:// in order to forward websockets upstream (it seems that no amount of setting headers will help you here).
If you set the protocol to ws://, then Apache will set the appropriate headers (Connection: Upgrade and Upgrade: websocket) when proxying requests upstream. However, for some reason, it does not seem to do this when proxying requests to itself / another VirtualHost.
You can see a blurb in VirtualHost *:80 (copied below) that tries to discern whether a websocket is in order and then change the protocol accordingly. This will not work for VirtualHost *:8080 (see the above bullet). Other methods are necessary.
RewriteCond %{HTTP:Upgrade} =websocket
RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket
RewriteRule /(.*) http://localhost:8080/$1 [P,L]
Put all of this together, and you have to communicate to VirtualHost *:8080 that a websocket connection is needed upstream. Luckily, we are in control of VirtualHost *:80 and can discern this information / pass it along. We must do so without touching the Connection or Upgrade headers, since Apache does weird things with them. The protocol may be traceable, but I am not sure how to do that. As a result, I use a bogus, internal, custom header for the transmission. Probably best to name it in such a way that collisions are unlikely.
In VirtualHost *:80, we add a block like:
SetEnvIf Upgrade ^websocket$ websock=true
RequestHeader set X-Is-Websocket %{websock}e
And then we read that header in VirtualHost *:8080, changing the protocol if necessary:
SetEnvIf X-Is-Websocket ^true$ websock=true
RequestHeader unset X-Is-Websocket
# change protocol if necessary
RewriteCond %{ENV:websock} =true
RewriteRule /(.*) ws://proxy-debug:8080/$1 [P,L]
RewriteCond %{ENV:websock} !=true
RewriteRule /(.*) http://proxy-debug:8080/$1 [P,L]
Hope it helps! :)
Related
We rebooted one of our servers over the weekend and now Apache won't pass through the host name - not sure whats wrong. Here's my config:
LoadModule headers_module modules/mod_headers.so
LoadModule env_module modules/mod_env.so
<IfModule headers_module>
<IfModule env_module>
PassEnv HOSTNAME
Header always set X-Host-Name "%{HOSTNAME}e"
</IfModule>
</IfModule>
I am running as a user other than root. I am able to execute sudo -u websrv "printenv" and the value for HOSTNAME appears appropriately.
Not sure whats going on
I have VPS server running ubuntu 20.04 (apache2) and is using http/1.1 and I want to upgrade to http/2
The domain is already configured using v-hosts (and has the ssl installed - lets encrypt).
Testing the current protocol:
$ curl -I https://my-domain.com
HTTP/1.1 200 OK
Date: Fri, 12 Mar 2021 17:32:01 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Steps that I done to upgrade to http/2:
1) sudo a2enmod http2
2) add in virtual host (ex: /etc/apache2/sites-enabled/my-domain.com-le-ssl.conf) the line:
Protocols h2 http/1.1
3) check the config
sudo apache2ctl configtest
4) Restart the web server
sudo systemctl reload apache2
Test again the ptotocol:
$ curl -I https://my-domain.com
HTTP/1.1 200 OK
Date: Fri, 12 Mar 2021 17:57:07 GMT
Server: Apache/2.4.41 (Ubuntu)
Upgrade: h2
Connection: Upgrade
Content-Type: text/html; charset=UTF-8
As you can see it still using the http/1.1
I also updated the apache2 from 2.4.41 -> 2.4.46 ... but the same problem.
What did I miss ???
** UPDATE **
apache2 loaded modules
$ sudo apachectl -M
Loaded Modules:
core_module (static)
so_module (static)
watchdog_module (static)
http_module (static)
log_config_module (static)
logio_module (static)
version_module (static)
unixd_module (static)
access_compat_module (shared)
alias_module (shared)
auth_basic_module (shared)
authn_core_module (shared)
authn_file_module (shared)
authz_core_module (shared)
authz_host_module (shared)
authz_user_module (shared)
autoindex_module (shared)
deflate_module (shared)
dir_module (shared)
env_module (shared)
expires_module (shared)
filter_module (shared)
headers_module (shared)
http2_module (shared)
mime_module (shared)
mpm_prefork_module (shared)
negotiation_module (shared)
php7_module (shared)
reqtimeout_module (shared)
rewrite_module (shared)
setenvif_module (shared)
socache_shmcb_module (shared)
ssl_module (shared)
status_module (shared)
php 7.4.3 installed
php
libapache2-mod-php
php-mysql
php-cli
php-mbstring
php-imagick
php-xml
php-gd
php-zip
php-curl
php-opcache
php-soap
php-bcmath
vhost config:
<VirtualHost *:80>
ServerAdmin admin#my-domain.com
ServerName my-domain.com
ServerAlias www.my-domain.com
DocumentRoot /var/www/my-domain.com/public
Protocols h2 http/1.1
<Directory /var/www/my-domain.com/public/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine on
RewriteCond %{SERVER_NAME} =my-domain.com [OR]
RewriteCond %{SERVER_NAME} =www.my-domain.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
and the ssl config:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin admin#my-domain.com
ServerName my-domain.com
ServerAlias www.my-domain.com
DocumentRoot /var/www/my-domain.com/public
Protocols h2 http/1.1
<Directory /var/www/my-domain.com/public/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/my-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/my-domain.com/privkey.pem
</VirtualHost>
</IfModule>
Replacing the mpm_prefork with mpm_event
using php-fpm (libapache2-mod-fcgid) instead of (libapache2-mod-php)
solve the problems.
You should probably add the --http2 option to your curl command
I have build apache httpd 2.4.37 from source in redhat and installed in home directory [/home/test/httpd-2.4.37]. as I don't have root access to install from yum. The server is running, I want to use this server only as a forward proxy.
I have the following modules in /home/test/httpd-2.4.37/conf/httpd.conf and the files in /home/test/httpd-2.4.37/modules
LoadModule proxy_module modules/mod_proxy.so LoadModule
proxy_connect_module modules/mod_proxy_connect.so LoadModule
proxy_ftp_module modules/mod_proxy_ftp.so LoadModule
proxy_http_module modules/mod_proxy_http.so LoadModule
proxy_fcgi_module modules/mod_proxy_fcgi.so LoadModule
proxy_scgi_module modules/mod_proxy_scgi.so LoadModule
proxy_uwsgi_module modules/mod_proxy_uwsgi.so LoadModule
proxy_fdpass_module modules/mod_proxy_fdpass.so LoadModule
proxy_wstunnel_module modules/mod_proxy_wstunnel.so LoadModule
proxy_ajp_module modules/mod_proxy_ajp.so LoadModule
proxy_balancer_module modules/mod_proxy_balancer.so
I have added below configuration at end of /home/test/httpd-2.4.37/conf/httpd.conf file.
Listen 127.0.0.1:8090
ProxyRequests On
ProxyVia On
ProxyPreserveHost Off
<Proxy "*">
Order deny,allow
Allow from all
</Proxy>
ErrorLog "/home/test/httpd-2.4.37/logs/proxy-error.log"
CustomLog "/home/test/httpd-2.4.37/logs/proxy-access.log" common
forward proxy is not working , when I call from a another machine using this proxy, I am getting connection refused as response.
no log is getting updated /home/test/httpd-2.4.37/logs/proxy-error.log & /home/test/httpd-2.4.37/logs/proxy-access.log
My primary objective is to tunnel ssh request over thru proxy. I have added the below change at the end of httpd.conf file. Proxy is working and forwarding the request to the destination.
Listen 8090
<VirtualHost *:8090>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} !^CONNECT [NC]
RewriteRule ^/(.*)$ - [F,L]
ProxyRequests On
ProxyBadHeader Ignore
ProxyVia On
AllowCONNECT 22 64
<Proxy "*">
Order deny,allow
Require ip 10
</Proxy>
ErrorLog "/home/test/httpd-2.4.37/logs/proxy-error.log"
CustomLog "/home/test/httpd-2.4.37/logs/proxy-access.log" common
</VirtualHost>
Consider the following configuration :
ProxyPass /myapp http://localhost:8080/myapp
ProxyPassReverse /myapp http://localhost:8080/myapp
Now requests that are made to http://www.example.com/myapp/foo/bar will be routed to my app with the expected results to the browser. However a request made to http://www.example.com/myapp/foo/bar/ will return a 404.
I observe the same results when I update the ProxyPass and ProxyPassReverse directives with the trailing forward slash.
I know that my Controller in my Spring app is set up to handle this URL pattern as calls made to http://localhost:8080/myapp/foo/bar/ work as expected.
I am using Apache 2.4 and Tomcat7. I have the following modules loaded:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule proxy_http_module modules/mod_proxy_http.so
This is driving me crazy.. Can't figure out whats wrong here..
Can't get mod_rewrite to work.. Nothing..
So here I try to redirect everyting to google.com
apache.conf (update)
<VirtualHost *:80>
ServerName www.domain.com
DocumentRoot /var/www/domain.com/public/www
RewriteEngine on
RewriteRule ^ http://www.google.com/ [L,R=301]
</VirtualHost>
apache.conf
RewriteEngine on
# Test
RewriteRule ^ http://www.google.com/ [L,R=301]
# redirect non-active subdomains to 'www'
RewriteCond %{HTTP_HOST} \.domain.com$ [NC]
RewriteCond %{HTTP_HOST} !^(sub|www)\. [NC]
RewriteRule ^ http://www.domain.com [L,R=301]
# put 'www' as subdomain if none is given
RewriteCond %{HTTP_HOST} ^domain.com$ [NC]
RewriteRule ^(.*) http://www.domain.com/$1 [L,R=301]
# www.domain.com
<VirtualHost *:80>
ServerName www.domain.com
DocumentRoot /var/www/domain.com/public/www
</VirtualHost>
# sub.domain.com
<VirtualHost *:80>
ServerName sub.domain.com
DocumentRoot /var/www/domain.com/public/sub
</VirtualHost>
Not working
Here you can see loaded modules
root#dyntest-amd-3700-2gb /var/www/dyntest.dk # apache2ctl -M
apache2: apr_sockaddr_info_get() failed for dyntest-amd-3700-2gb
apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
[Thu Jan 30 14:33:34 2014] [warn] NameVirtualHost *:80 has no VirtualHosts
Loaded Modules:
core_module (static)
log_config_module (static)
logio_module (static)
version_module (static)
mpm_prefork_module (static)
http_module (static)
so_module (static)
alias_module (shared)
auth_basic_module (shared)
authn_file_module (shared)
authz_default_module (shared)
authz_groupfile_module (shared)
authz_host_module (shared)
authz_user_module (shared)
autoindex_module (shared)
cgi_module (shared)
deflate_module (shared)
dir_module (shared)
env_module (shared)
geoip_module (shared)
mime_module (shared)
negotiation_module (shared)
php5_module (shared)
reqtimeout_module (shared)
setenvif_module (shared)
status_module (shared)
expires_module (shared)
headers_module (shared)
rewrite_module (shared)
ssl_module (shared)
Syntax OK
Add the following to your .htaccess: ThisIsNotSupposedToWork!!!!. If that doesn't generate an internal server error, your AllowOverride is set to None.