How to route trafic to local home battery with web interface via on-line apache webserver?

Hi I run an up-to-date RL 9 and apache on my https webserver. Working great.
I want to get access to my local Battery Management System (HomeVolt) at 192.168.1.222 with its own (unsecure) http interface through my secure web server via https://myserver/HomeVolt/battery
I tried this, but this doesn’t work (part of httpd.conf of web server):

….

RewriteEngine on

RewriteBase “/HomeVolt/”

RewriteRule “^widget/(.*)$” “http://192.168.1.222/battery/widget/$1” [P]

ProxyPassReverse “/HomeVolt/widget/” “http://192.168.1.222/battery/widget/”

……..

The result is:

[root@server slide_shows]# systemctl status httpd
× httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
Drop-In: /etc/systemd/system/httpd.service.d
└─php-fpm.conf
Active: failed (Result: exit-code) since Thu 2026-04-09 12:55:38 CEST; 8s ago
Duration: 1h 5min 19.851s
Docs: man:httpd.service(8)
Process: 2223212 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE)
Main PID: 2223212 (code=exited, status=1/FAILURE)
Status: “Reading configuration…”
CPU: 17ms

Apr 09 12:55:38 server.hartings.se systemd[1]: Starting The Apache HTTP Server…
Apr 09 12:55:38 server.hartings.se httpd[2223212]: AH00526: Syntax error on line 432 of /etc/httpd/conf/httpd.conf:
Apr 09 12:55:38 server.hartings.se httpd[2223212]: RewriteBase: only valid in per-directory config files
Apr 09 12:55:38 server.hartings.se systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Apr 09 12:55:38 server.hartings.se systemd[1]: httpd.service: Failed with result ‘exit-code’.
Apr 09 12:55:38 server.hartings.se systemd[1]: Failed to start The Apache HTTP Server.

The Rewrite and ProxyReverse lines I found in the general apache manuals (Using mod_rewrite for Proxying - Apache HTTP Server Version 2.4)
I am not sure about lines with “widget”. Is this a general command or just an example?

Any sugestions on how to route this BMS traffic through /HomeVolt/ directory on my own webserver?
Many thanks for any hints pointing me in the right direction!

Your apache error is referencing the RewriteBase line. I don’t have any advice about that line specifically – I tend to only use mod_rewrite for websocket upgrades and forcing https. But here’s another apache doc link that should be relevant: Reverse Proxy Guide - Apache HTTP Server Version 2.4

And here’s an example from my config:

Redirect "/gitbucket" "/gitbucket/"
ProxyPass /gitbucket/ https://10.0.0.166/gitbucket/
<Location /gitbucket/>
    ProxyPassReverse /gitbucket/
    ProxyPreserveHost On
    RequestHeader unset Accept-Encoding
</Location>

It’s worth noting that the application in my example is at the same relative path /gitbucket/ on both the apache web server and the internal server.

Changing this relative path in apache can become complicated, depending on your application. So if your internal url is http://192.168.1.222/battery/, you may want to try mapping it to https://server/battery/ before attempting the more complicated rewrites.

All apache directives have a context.

From official docs:

RewriteBase Directive
Context directory, .htaccess

it means that the directive can only be used either in a Directory block or in an htaccess file (residing in some dir).

Thanks @linde !

I will check the apache pages you refer to and give the simple “Proxy commands” a try. What additional functionality adds your solution, not provided by the simpler “Proxy"-commands in the apache link you provided?

Is your local URL the one with “10.0.0” in the address? Just trying to find out how I should read you lines and what I should write in my case.

To make my lines similar to yours, I will use the same name (“battery”) on de web server and the local URL.

Thanks @gerry666uk for your input.

I clearly must do some more reading and learning to fully understand the implication of your comment.

I hope I may revert later with some more questions :slight_smile:

Hi @linde
I tried several options:

First of all, the ProxyPass/ProxyPassRevers method as in your official apache link:
( I created the folder https://myserver/battery on my web server):
….
ProxyPass ”/battery” "http://192.168.1.222/battery/”
ProxyPassReverse ”/battery” "http://192.168.1.222/battery/”
….

No errors when restarting httpd. Good.
The web server response on https://myserver/battery was:
Header fields are too long

I then cleared the cache (as recommended on some sites I read related to this specifc message), but this did not change anything. Same failure (Header fields are too long) .

I also tried to link to the base directory of the BMS, using:

….
ProxyPass ”/battery” "http://192.168.1.222/”
ProxyPassReverse ”/battery” "http://192.168.1.222/”
….

This gave the same error (Header fields are too long) .

Secondly, I tried to use a “copy” of your syntax:

Redirect “/battery” “/battery/”
ProxyPass “/battery/” “http://192.168.1.222/battery/”
<Location /battery/>
ProxyPassReverse “/battery/”
ProxyPreserveHost On
RequestHeader unset Accept-Encoding

This gives the same respons (Header fields are too long)
I then checked the RewriteRule (using this apache page):

RewriteRule ”^/battery$" "http://192.168.1.222/” [R]

This just opens the empty folder on my web server https://myserver/battery
It does not seem to make the link to the BMS web interface.

Last atempt, use plain Redirect:

Redirect “/battery” “http://192.168.1.222

This actually works, but only within my local network - but not from the internet.

So I am stuck…. Any hints what to do next? Thanks for looking and trying to help”

Header fields too long usually means you’ve created an infinite loop.

The apache log files might tell you what you’re doing wrong.

Thanks Gerry. What code did create this loop? I can’t find it in those few lines…
Den 10 apr. 2026, kI 19:47, Gerry Hickman <notifications@rockylinux.discoursemail.com> skrev:

Your trials look pretty clueless :slight_smile:

  1. Get rid of all the Redirect and Rewriterule stuff. One or more of them creates the loop. You need the reverse-proxy (ProxyPass) only.
  2. The Location directive needs an /Location in order to end the location block (that is missing in your posted example).
  3. Inside a location block, you only need a ProxyPass (the corresponding ProxyPassreverse is generated automatically)
  4. Very important: For security reasons, If using mod_proxy (implicated by ProxyPass) you should disable in your config’s toplevel (outside of the Location block)

So here is your only (no Redirect or Rewrite) config:

# For security reasons
ProxyRequests off
# This defines the location (on your server) which - when requested - forwards to your battery
<Location /battery>
    ProxyPass http://your.battry-ipaddress/
</Location>

If it still loops after that, then post your complete config, not just snippets.
And regarding the “ProxyPreserveHost On”: This might be required (after the proxypass line), but first try without it - it depends on the web-interface of your battery.
There are several other flags like this which modify the reverse-proxy funcionality. All of them depend on the battery’s web interface. Read the doc at apache.org

Thanks @felfert !

To comment on your items:

item 1: I removed all of this.
item 3: done
item 4: done

item 2: Sorry for not giving the correct info.
This must be a bug in the quote-block (, ,)- button. I have indeed a line with at the end of my code, but this gets automatically removed(!) when I copy it inside this blockcode !!!
This line is indeed at the end of this section!! Something for the forum admin to look into?
I took a screendump to prove the line is there :slight_smile:

The result is no errors, but no response at all, just a blank page on my browser (tried firefox and chrome) when using https://myserver/battery

I also tried to add/remove the “ProxyPreserveHost On” line, but this didn’t change anything.
I also added a timeout as this battery web interface is quite slow. This didn’t help though.

I guess it’s correct that /var/www/html/battery on my server should be a directory, not a file?

Are there any other things I could try? I have no clue what other flags could be required. When directly locally addressing the battery web interface, it works without any issue (but slow).

Many thanks for reading and trying to help me out!

Nope, you don’t need it at all. In fact, you should probably remove it, so that it does not get shown accidently.

Without having access to the actual battery system, everything would be pure guesswork.

You could at least test, if proxying works in general. So you can temporarily replace your current ProxyPass line by:

ProxyPass "http://www.google.com"
# Note the http, NOT https!

After reloading your httpd, Browsing to https://your.server/battery should immediately redirect your browser to https://www.google.com. If that gives you an empty page as well, then there is something else in your config which prevents your httpd reverse-proxy from working.

Thanks! I did that and the result is an immediate and normal reply from www.google.com !
So the syntax is correct and the command is working!
I guess there is something special with the battery web interface….

Any more things I could test? Typical requirements from such web interfaces you are aware of?

Please be warned, that publicly posting content from your battery interface you might expose private information.
Having said that, you could do the following:

On your apache server machine run the following commands:

  1. curl -vL http://192.168.1.222 > battery-direct.log 2>&1
  2. curl -vL https://your.server/battery > battery-proxied.log 2>&1

Then post these 2 files here and I can look for any hints.

Again: Do NOT do this if you value privacy

Apart from that, I have no more ideas - the silly things that IOT developers can do with their interfaces do not have any limits.

1 Like

Thanks! Here are the contents two files (I could not attach the files themselves, only pictures-files)

I replaced sensitive info with “xxxxxx” in the Proxy.log .

The Direct.log seem to also includes binary data.

Hope you can make sense of this!

Proxy-log:

# more battery-proxied.log

% Total % Received % Xferd Average Speed Time Time Time Current

                             Dload  Upload   Total   Spent    Left  Speed

* Connected to xxxxxxxxx, IP xxxxxxxxxxx port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* CAfile: /etc/pki/tls/certs/ca-bundle.crt

* TLSv1.0 (OUT), TLS header, Certificate Status (22):

} [5 bytes data]

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

} [512 bytes data]

* TLSv1.2 (IN), TLS header, Certificate Status (22):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Server hello (2):

{ [122 bytes data]

* TLSv1.2 (IN), TLS header, Finished (20):

{ [5 bytes data]

* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):

{ [1 bytes data]

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

{ [25 bytes data]

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Certificate (11):

{ [2590 bytes data]

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

{ [264 bytes data]

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Finished (20):

{ [52 bytes data]

* TLSv1.2 (OUT), TLS header, Finished (20):

} [5 bytes data]

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

} [1 bytes data]

* TLSv1.2 (OUT), TLS header, Unknown (23):

} [5 bytes data]

* TLSv1.3 (OUT), TLS handshake, Finished (20):

} [52 bytes data]

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use http/1.1

* Server certificate:

* subject: CN=xxxxxxxxx

* start date: Apr 2 05:28:01 2026 GMT

* expire date: Jul 1 05:28:00 2026 GMT

* subjectAltName: host ”xxxxxxxxx” matched cert’s ”xxxxxxx”

* issuer: C=US; O=Let’s Encrypt; CN=R13

* SSL certificate verify ok.

* TLSv1.2 (OUT), TLS header, Unknown (23):

} [5 bytes data]

> GET /battery HTTP/1.1

> Host: xxxxxxxxx

> User-Agent: curl/7.76.1

> Accept: */*

>

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

{ [281 bytes data]

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

{ [281 bytes data]

* old SSL session ID is stale, removing

* TLSv1.2 (IN), TLS header, Unknown (23):

{ [5 bytes data]

* Mark bundle as not supporting multiuse

< HTTP/1.1 401 Unauthorized

< Date: Sat, 11 Apr 2026 16:32:00 GMT

< Server: Apache/2.4.62 (Rocky Linux) OpenSSL/3.5.1

< WWW-Authenticate: Basic realm=“For admin”

< Content-Length: 381

< Content-Type: text/html; charset=iso-8859-1

<

{ [381 bytes data]

* Connection #0 to host xxxxxxxxxx left intact

401 Unauthorized

Unauthorized

This server could not verify that you

are authorized to access the document

requested. Either you supplied the wrong

credentials (e.g., bad password), or your

browser doesn’t understand how to supply

the credentials required.

[root@server Downloads]#

and the direct.log file:

# more battery-direct.log

* Trying 192.168.1.222:80…

% Total % Received % Xferd Average Speed Time Time Time Current

                             Dload  Upload   Total   Spent    Left  Speed

> GET / HTTP/1.1

> Host: 192.168.1.222

> User-Agent: curl/7.76.1

> Accept: */*

>

* Mark bundle as not supporting multiuse

< HTTP/1.1 200 OK

< Content-Type: text/html

< Transfer-Encoding: chunked

< connection: keep-alive

< Content-Encoding: gzip

<

{ [187 bytes data]

* Connection #0 to host 192.168.1.222 left intact

MA ^LEY[w.KOt

tJl3OG]^L(<=ZlGXVLPRP<wY

aF\eUw^L?qFePLoSdɱ7’lIP$&lGs٭WLn)!r(/{

[root@server Downloads]#

What can you conclude from these logfiles?

Look out for gzip encoding, matching between the request and response. curl normally matches it correctly, but it also has a --compressed option.

This one is needing a username and password, so try this, putting in your username and password:

curl -vL -u ‘username:password’ https://your.server/battery > battery-proxied.log 2>&1

And as Gerry said, try adding the ==compressed option to this one:

curl -vL --compressed http://192.168.1.222 > battery-direct.log 2>&1

@dannyw99 and @gerry666uk

I now used the –compressed option for both cases, the direct and the proxy version:

[root@server Downloads]# curl -vL --compressed http://192.168.1.222 > battery-direct.log 2>&1
[root@server Downloads]# more battery-direct.log 2>&1
*   Trying 192.168.1.222:80...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

> GET / HTTP/1.1
> Host: 192.168.1.222
> User-Agent: curl/7.76.1
> Accept: */*
> Accept-Encoding: deflate, gzip, br
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/html
< Transfer-Encoding: chunked
< connection: keep-alive
< Content-Encoding: gzip
< 
{ [192 bytes data]

* Connection #0 to host 192.168.1.222 left intact
<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="/main.js" charset="utf-8"></script>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>

  <body></body>
</html>
[root@server Downloads]#

Regarding the proxy-version-log, the battery’s web interface doesn’t require a login/password anymore .It was used before, but I removed it for now to avoid any issues…..
The result after I removed the user:passwd for https:/ /my.server/battery is “similar” as for the direct connection:

[root@server Downloads]# curl -vL --compressed https://my.server/battery > battery-proxied.log 2>&1
[root@server Downloads]# more battery-proxied.log 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

* Connected to my.server (192.168.1.94) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.2 (IN), TLS header, Certificate Status (22):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.2 (IN), TLS header, Finished (20):
{ [5 bytes data]
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [25 bytes data]
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [2590 bytes data]
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.2 (OUT), TLS header, Finished (20):
} [5 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS header, Unknown (23):
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=my.domain
*  start date: Apr  2 05:28:01 2026 GMT
*  expire date: Jul  1 05:28:00 2026 GMT
*  subjectAltName: host "my.server" matched cert's "my.domain"
*  issuer: C=US; O=Let's Encrypt; CN=R13
*  SSL certificate verify ok.
* TLSv1.2 (OUT), TLS header, Unknown (23):
} [5 bytes data]
> GET /battery HTTP/1.1
> Host: hartings.se
> User-Agent: curl/7.76.1
> Accept: */*
> Accept-Encoding: deflate, gzip, br
> 
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [281 bytes data]
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [281 bytes data]
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Unknown (23):
{ [5 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 13 Apr 2026 14:00:54 GMT
< Server: Apache/2.4.62 (Rocky Linux) OpenSSL/3.5.1
< Content-Type: text/html; charset=UTF-8
< Content-Encoding: gzip
< Transfer-Encoding: chunked
< 
{ [192 bytes data]

* Connection #0 to host my.server left intact
<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="/main.js" charset="utf-8"></script>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>

  <body></body>
</html>
[root@server Downloads]# 

So what does this mean? It looks like a normal html page using java. So what is the problem?
The result is still the same, a blank page in the browser when using the lines below in httpd.conf:


....
# For security reasons
ProxyRequests off
# This defines the location (on your server) which - when requested - forwards to your battery
<Location "/battery">
     ProxyPass "http://192.168.1.222"
#     ProxyPreserveHost On
</Location>
.....

Thank you very mucj for your help!