I wanted to switch from uhttpd to Apache, because I could use it for hosting LuCi, and also to reverse-proxy to my home server and add SSL/TLS security.
At first, I saw this old topic, and found out that indeed, there is no proper guide how to make the webserver and LuCi work.
To generate certificates I am using acme, which can be downloaded as a package in OpenWRT. Unfortunately, it’s not so well documented yet. Hopefully, this will help those who couldn't configure it with uci yet.
I’m not an expert and struggled a lot to make this work. Therefore I want to share a small guide of what I have done. If somebody with more experience sees this, any suggestion is welcome toti improve this
1. Install the packages:
apache
apache-mod-proxy
apache-mod-ssl
acme
acme-dnsapi
Install them from the interface, or terminal, how you prefer.
2. Apache configuration
Apache configuration is created in /etc/apache
. The extra
directory contains some configuration examples. I made a backup of the original apache2.conf there.
Here is my apache2.conf:
ServerRoot "/usr"
Listen _RouterLocalIP_:80
Listen 443
LoadModule mpm_prefork_module lib/apache2/mod_mpm_prefork.so
LoadModule authn_file_module lib/apache2/mod_authn_file.so
LoadModule authn_core_module lib/apache2/mod_authn_core.so
LoadModule authz_host_module lib/apache2/mod_authz_host.so
LoadModule authz_groupfile_module lib/apache2/mod_authz_groupfile.so
LoadModule authz_user_module lib/apache2/mod_authz_user.so
LoadModule authz_core_module lib/apache2/mod_authz_core.so
LoadModule access_compat_module lib/apache2/mod_access_compat.so
LoadModule auth_basic_module lib/apache2/mod_auth_basic.so
LoadModule watchdog_module lib/apache2/mod_watchdog.so
LoadModule reqtimeout_module lib/apache2/mod_reqtimeout.so
LoadModule filter_module lib/apache2/mod_filter.so
LoadModule mime_module lib/apache2/mod_mime.so
LoadModule log_config_module lib/apache2/mod_log_config.so
LoadModule env_module lib/apache2/mod_env.so
LoadModule headers_module lib/apache2/mod_headers.so
LoadModule setenvif_module lib/apache2/mod_setenvif.so
LoadModule version_module lib/apache2/mod_version.so
LoadModule proxy_module lib/apache2/mod_proxy.so
LoadModule proxy_http_module lib/apache2/mod_proxy_http.so
LoadModule proxy_wstunnel_module lib/apache2/mod_proxy_wstunnel.so
LoadModule ssl_module lib/apache2/mod_ssl.so
LoadModule unixd_module lib/apache2/mod_unixd.so
LoadModule status_module lib/apache2/mod_status.so
LoadModule autoindex_module lib/apache2/mod_autoindex.so
LoadModule cgi_module lib/apache2/mod_cgi.so
LoadModule dir_module lib/apache2/mod_dir.so
LoadModule alias_module lib/apache2/mod_alias.so
LoadModule rewrite_module lib/apache2/mod_rewrite.so
<IfModule unixd_module>
#
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
#LuCI needs root privileges to access configuration
# User apache
# Group apache
</IfModule>
<IfModule dir_module>
DirectoryIndex index.html
</IfModule>
<Files ".ht*">
Require all denied
</Files>
#
# ErrorLog: The location of the error log file.
# If you do not specify an ErrorLog directive within a <VirtualHost>
# container, error messages relating to that virtual host will be
# logged here. If you *do* define an error logfile for a <VirtualHost>
# container, that host's errors will be logged there and not here.
#
ErrorLog "/tmp/apache.log"
#
# LogLevel: Control the number of messages logged to the error_log.
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
#
LogLevel error
<IfModule log_config_module>
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
#
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
# You need to enable mod_logio.c to use %I and %O
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
#
# The location and format of the access logfile (Common Logfile Format).
# If you do not define any access logfiles within a <VirtualHost>
# container, they will be logged here. Contrariwise, if you *do*
# define per-<VirtualHost> access logfiles, transactions will be
# logged therein and *not* in this file.
#
CustomLog "/var/log/apache2/access_log" common
#
# If you prefer a logfile with access, agent, and referer information
# (Combined Logfile Format) you can use the following directive.
#
#CustomLog "/var/log/apache2/access_log" combined
</IfModule>
<IfModule alias_module>
ScriptAlias /cgi-bin/ "/www/cgi-bin/"
</IfModule>
<IfModule headers_module>
#
# Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
# backend servers which have lingering "httpoxy" defects.
# 'Proxy' request header is undefined by the IETF, not listed by IANA
#
RequestHeader unset Proxy early
</IfModule>
<IfModule mime_module>
#
# TypesConfig points to the file containing the list of mappings from
# filename extension to MIME-type.
#
TypesConfig /etc/apache2/mime.types
#
# AddType allows you to add to or override the MIME configuration
# file specified in TypesConfig for specific file types.
#
#AddType application/x-gzip .tgz
#
# AddEncoding allows you to have certain browsers uncompress
# information on the fly. Note: Not all browsers support this.
#
#AddEncoding x-compress .Z
#AddEncoding x-gzip .gz .tgz
#
# If the AddEncoding directives above are commented-out, then you
# probably should define those extensions to indicate media types:
#
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
#
# AddHandler allows you to map certain file extensions to "handlers":
# actions unrelated to filetype. These can be either built into the server
# or added with the Action directive (see below)
#
# To use CGI scripts outside of ScriptAliased directories:
# (You will also need to add "ExecCGI" to the "Options" directive.)
#
#AddHandler cgi-script .cgi
# For type maps (negotiated resources):
#AddHandler type-map var
#
# Filters allow you to process content before it is sent to the client.
#
# To parse .shtml files for server-side includes (SSI):
# (You will also need to add "Includes" to the "Options" directive.)
#
#AddType text/html .shtml
#AddOutputFilter INCLUDES .shtml
</IfModule>
<IfModule proxy_html_module>
Include /etc/apache2/extra/proxy-html.conf
</IfModule>
ServerAdmin _YourEmail_
ServerName _RouterLocalIP_
ServerTokens Prod
ServerSignature Off
<Directory />
AllowOverride none
Require all denied
</Directory>
DocumentRoot "/www"
<Directory "/www">
Options Indexes FollowSymLinks
AllowOverride None
Require ip _YourLocalNetwork_
</Directory>
<Directory "/www/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Require ip _YourLocalNetwork_
</Directory>
<VirtualHost *:443>
ServerName _subdomain.subdomain.domain.com_
SSLEngine on
SSLCertificateFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.cer"
SSLCertificateChainFile "/etc/acme/_subdomain.domain.com_/fullchain.cer"
SSLCertificateKeyFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.key"
DocumentRoot "/www"
<Directory "/www">
AuthType Basic
AuthName "Restricted Content"
AuthUserFile /etc/apache2/.htpasswd
Require user _userGenerated_
Options Indexes FollowSymLinks
AllowOverride None
</Directory>
<Directory "/www/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:443>
ServerName _subdomain.domain.com_
SSLEngine on
SSLCertificateFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.cer"
SSLCertificateChainFile "/etc/acme/_subdomain.domain.com_/fullchain.cer"
SSLCertificateKeyFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.key"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "same-origin"
ProxyPreserveHost On
ProxyRequests off
ProxyPass /api/websocket ws://_hostLocalIP_:8123/api/websocket
ProxyPassReverse /api/websocket ws://_hostLocalIP_:8123/api/websocket
ProxyPass / http://_hostLocalIP_:8123/
ProxyPassReverse / http://_hostLocalIP_:8123/
RewriteEngine on
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://_hostLocalIP_:8123/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://_hostLocalIP_:8123/$1 [P,L]
</VirtualHost>
<VirtualHost *:443>
ServerName _subdomain.subdomain.domain.com_
SSLEngine on
SSLCertificateFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.cer"
SSLCertificateChainFile "/etc/acme/_subdomain.domain.com_/fullchain.cer"
SSLCertificateKeyFile "/etc/acme/_subdomain.domain.com_/_subdomain.domain.com_.key"
ProxyPreserveHost On
ProxyPass / http://_hostLocalIP_:_port_/_customLocation/
`ProxyPassReverse` / http://_hostLocalIP_:_port_/_customLocation_/
<Location "/">
AuthType Basic
AuthName "Restricted Content"
AuthUserFile /etc/apache2/.htpasswd
Require user _userGenerated_
</Location>
</VirtualHost>
I wanted that LuCi to be accessible via http only from local network, this is why I bound the router's local IP to the 80 port (Listen _RouterLocalIP_:80
). For more safety, I added Require ip _YourLocalNetwork_
to the directories, to make sure it's accessible only from the local network. Put there for ex. 192.168.1
, the bytes that stands for your network address.
So this apache configuration, without the <VirtualHost *:443>
directives will respond only to local http requests. The first VirtualHost
is how I am accessing LuCi externally via https, the second one is a reverse-proxy to my Home Assistant server, a third one is a generic example, how a basic reverse-proxy works.
I added additional authentication to certain pages, you can also generate users with these commands:
htpasswd -c /etc/apache2/.htpasswd _userGenerated_
htpasswd /etc/apache2/.htpasswd _userGenerated2_
The option -c
is needed only the first time, to create the specified file.
Otherwise, delete these lines:
AuthType Basic
AuthName "Restricted Content"
AuthUserFile /etc/apache2/.htpasswd
Require user _userGenerated_
Don't forget the /
at the end of every address at the ProxyPass
and ProxyPassReverse
directives!
3. ACME configuration
When using ACME from OpenWRT you have to configure the parameters via uci, and then the acme service will call the acme.sh periodically based on your configuration. Sadly, the uci options are not documented. I installed luci-app-acme
, made a working config from the interface, and then I looked at the /etc/config/acme
configuration. Here's mine:
config acme
option state_dir '/etc/acme'
option account_email '_youEmail_'
option debug '1'
config cert 'dynu_wildcard'
option validation_method 'dns'
option update_uhttpd '0'
option days '60'
option use_staging '0'
option keylength '2048'
option dns 'dns_dynu'
option enabled '1'
option use_staging '0'
list domains '_subdomain.domain.com_'
list domains '*._subdomain.domain.com_'
As you saw, I'm accessing my services by different subdomains, this is why I needed a wildcard certificate. Before this I was using duckdns, but after hours of investigations I realized that their API is not compatible with acme, and could not generate a wildcard certificate. I moved to dynu after that.
The apache configuration can read the certificates directly from /etc/acme/...
, so basically that's it!
Don't forget to stop and disable (prevent to autostart at reboot) uhttpd.
Useful info:
/etc/init.d/uhttpd stop
/etc/init.d/uhttpd disable
/etc/init.d/apache2 start/stop/restart
/etc/init.d/acme start/stop/restart
apache log location (with this config): /tmp/apache.log
NOTE: In this guide, I read that
LuCI needs root privileges to access configuration, so lighttpd needs to run as root too, so edit the configuration file
/etc/lighttpd/lighttpd.conf
so I commented out
# User root
# Group root`
lines in apache2.conf, in order to have root privileges. Because of that the apache log contains entries of these:
[unixd:alert] [pid 2938] (2)No such file or directory: AH02155: getpwuid: couldn't determine user name from uid -1, you probably need to modify the User directive
Does somebody know, how should this be corrected??