Run OpenConnect VPN Server & Apache/Nginx on the Same Box with HAProxy
This tutorial will be showing you how to run OpenConnect VPN server (ocserv) and Apache/Nginx on the same box with HAProxy. OpenConnect (ocserv) is an open-source implementation of the Cisco AnyConnect VPN protocol.
Prerequisites
To follow this tutorial, it’s assumed that you have already set up an OpenConnect VPN server with Let’s Encrypt TLS server certificate. If not, please follow one of the following tutorials.
- Set Up OpenConnect VPN Server (ocserv) on Ubuntu 20.04 with Let’s Encrypt
- Set Up OpenConnect VPN Server (ocserv) on Ubuntu 16.04/18.04 with Let’s Encrypt
- Set Up OpenConnect VPN Server (ocserv) on Debian 10 Buster with Let’s Encrypt
- Set Up OpenConnect VPN Server (ocserv) on CentOS 8/RHEL 8 with Let’s Encrypt
Make OpenConnect VPN server and web server use port 443 at the same time
By default, OpenConnect VPN server listens on port 443. If you already have Apache/Nginx listening on port 443, then ocserv can’t bind to port 443. You can configure ocserv to listen on another port, but it will require end-users to specify the port in client software, which you should avoid if you care about user experience. Also, TLS traffic on TCP port 443 usually enjoys higher priority in QoS (Quality of Service), so you will have better speed.
Normally a port can only be used by one process. However, we can use HAproxy (High Availability Proxy) and SNI (Server Name Indication) to make ocserv and Apache/Nginx use port 443 at the same time.
Ocserv Configuration
First, edit ocserv configuration file.
sudo nano /etc/ocserv/ocserv.conf
Uncomment the following line. This will allow ocserv to obtain the client IP address instead of HAproxy IP address.
listen-proxy-proto = true
Then find the following line.
#listen-host = [IP|HOSTNAME]
Change it to
listen-host = 127.0.0.1
This will make ocserv listen on 127.0.0.1 because later HAproxy will need to listen on the public IP address. Save and close the file. Then restart ocserv.
sudo systemctl restart ocserv
Next, we also need to make the web server listen on localhost only, instead of listening on public IP address.
Nginx Configuration
If you use Nginx, edit the server block file.
sudo nano /etc/nginx/conf.d/example.com.conf
In the SSL server block, find the following directive.
listen 443 ssl;
Change it to
listen 127.0.0.2:443 ssl;
This time we make it listen on 127.0.0.2:443
because 127.0.0.1:443
is already taken by ocserv. Save and close the file. The Nginx main configuration file /etc/nginx/nginx.conf
and the default server block /etc/nginx/sites-enabled/default
might include a default virtual host listening on 443, so you might need to edit this file too.
Then restart Nginx.
sudo systemctl restart nginx
Apache Configuration
If you use Apache web server, edit your virtual host file.
Debian/Ubuntu
sudo nano /etc/apache2/sites-enabled/example.com.conf
CentOS/RHEL
sudo nano /etc/httpd/conf.d/example.com.conf
In the SSL virtual host, change
<VirtualHost *:443>
To
<VirtualHost 127.0.0.2:443>
This time we make it listen on 127.0.0.2:443
because 127.0.0.1:443
is already taken by ocserv. Save and close the file.
Then edit the /etc/apache2/ports.conf
file on Debian/Ubuntu.
sudo nano /etc/apache2/ports.conf
Edit the/etc/httpd/conf.d/ssl.conf
file on CentOS/RHEL.
sudo nano /etc/httpd/conf.d/ssl.conf
Change
Listen 443
To
Listen 127.0.0.2:443
Save and close the file. Restart Apache.
sudo systemctl restart apache2
or
sudo systemctl restart httpd
HAProxy Configuration
Now install HAproxy.
sudo apt install haproxy
or
sudo dnf install haproxy
Start HAProxy
sudo systemctl start haproxy
Edit configuration file.
sudo nano /etc/haproxy/haproxy.cfg
If you use Nginx, copy and paste the following lines to the end of the file. Replace 12.34.56.78
with the public IP address of your server. Replace vpn.example.com
with the domain name used by ocserv and www.example.com
with the domain name used by your web server.
frontend https bind 12.34.56.78:443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } use_backend ocserv if { req_ssl_sni -i vpn.example.com } use_backend nginx if { req_ssl_sni -i www.example.com } use_backend nginx if { req_ssl_sni -i example.com } default_backend ocserv backend ocserv mode tcp option ssl-hello-chk # pass requests to 127.0.0.1:443. Proxy protocol (v2) header is required by ocserv. server ocserv 127.0.0.1:443 send-proxy-v2 backend nginx mode tcp option ssl-hello-chk server nginx 127.0.0.2:443 check
If you use Apache, copy and paste the following lines to the end of the file. Replace 12.34.56.78
with the public IP address of your server. Replace vpn.example.com
with the domain name used by ocserv and www.example.com
with the domain name used by your web server.
frontend https bind 12.34.56.78:443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } use_backend ocserv if { req_ssl_sni -i vpn.example.com } use_backend apache if { req_ssl_sni -i www.example.com } use_backend apache if { req_ssl_sni -i example.com } default_backend ocserv backend ocserv mode tcp option ssl-hello-chk # pass requests to 127.0.0.1:443. Proxy protocol (v2) header is required by ocserv. server ocserv 127.0.0.1:443 send-proxy-v2 backend apache mode tcp option ssl-hello-chk server apache 127.0.0.2:443 check
Save and close the file. Then restart HAproxy.
sudo systemctl restart haproxy
In the configuration above, we utilized the SNI (Server Name Indication) feature in TLS to differentiate VPN traffic and normal HTTPS traffic.
- When
vpn.example.com
is in the TLS Client Hello, HAProxy redirect traffic to the ocserv backend. - When
www.example.com
is in the TLS Client Hello, HAProxy redirect traffic to the apache/nginx backend. - If the client doesn’t specify the server name in TLS Client Hello, then HAproxy will use the default backend (ocserv).
You can test this setup with the openssl
tool. First, run the following command multiple times.
echo | openssl s_client -connect your-server-IP:443 | grep subject
We didn’t specify server name in the above command, so HAproxy will always pass the request to the default backend (ocserv), and its certificate will be sent to the client. Next, run the following two commands.
echo | openssl s_client -servername www.example.com -connect your-server-IP:443 | grep subject echo | openssl s_client -servername vpn.example.com -connect your-server-IP:443 | grep subject
Now we specified the server name in the commands, so HAproxy will pass requests according to the SNI rules we defined. Note that the Cisco AnyConnect App doesn’t support TLS SNI, so it’s better to set ocserv
as the default backend in HAProxy configuration file.
When renewing Let’s Encrypt certificate for your website, it’s recommended that you use the http-01
challenge instead of tls-alpn-01
challenge, because HAproxy is listening on port 443 of the public IP address, so it can interfere with the renewal process.
sudo certbot renew --preferred-challenges http-01
Fixing HAproxy Error
If your Apache/Nginx website doesn’t show up in your browser and you see the following messages in haproxy log (/var/log/haproxy.log
)
Server nginx/nginx is DOWN, reason: Socket error, info: "Connection reset by peer backend nginx has no server available! Layer6 invalid response
It might be your backend Nginx web server is using a TLS certificate with OCSP must staple extension. Nginx doesn’t send the OCSP staple information on the first HTTP request. To make it work, be sure to add a resolver in your Nginx virtual host configuration like below.
{
....
ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/chain.pem;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8;
....
}
Save and close the file. Then restart Nginx.
sudo systemctl restart nginx
Also, consider removing the health check for the backend server in HAproxy. So change
server nginx 127.0.0.2:443 check
To
server nginx 127.0.0.2:443
Save and close the file. Then restart HAproxy.
sudo systemctl restart haproxy
How to Enable IPv6 in ocserv with HAProxy
First, create AAAA record for vpn.example.com
in your DNS zone editor, so when you finish setting up IPv6 in ocserv, the DNS record should be propagated to the Internet.
Testing IPv6 Connectivity
To establish VPN tunnel in IPv6 protocol, make sure the VPN server has a public IPv6 address. (The VPN client doesn’t have to have a public IPv6 address.) To find out, run the following command.
ip addr
Locate the main network interface. If you can find a inet6 .... scope global
line like below, then you have a public IPv6 address. The inet6
address with scope link
is a private IPv6 address.
Then go to https://test-ipv6.com/ to check your IPv6 connectivity. If the VPN client has a public IPv6 address, it might tell you that your VPN is only protecting one protocol, not both. That’s because we didn’t enable IPv6 in ocserv.
Enable IPv6 in ocserv
To enable IPv6 in ocserv, edit ocserv configuration file.
sudo nano /etc/ocserv/ocserv.conf
Find the following two lines and uncomment them, so VPN clients will be given private IPv6 addresses.
ipv6-network = fda9:4efe:7e3b:03ea::/48 ipv6-subnet-prefix = 64
If you see the following line
ipv6-network = fda9:4efe:7e3b:03ea::/64
Please change it to:
ipv6-network = fda9:4efe:7e3b:03ea::/48
Save and close the file. Restart ocserv for the change to take effect.
sudo systemctl restart ocserv
Enable IP Forwarding for IPv6
Then we need to enable IP forwarding for IPv6 in the Linux kernel. Edit sysctl.conf
file.
sudo nano /etc/sysctl.conf
Add the following line at the end of this file.
net.ipv6.conf.all.forwarding=1
Save and close the file. Then apply the changes with the below command.
sudo sysctl -p
Set Up IPv6 in Firewall (Debian, Ubuntu)
Next, we need to set up IPv6 masquerading in the UFW firewall, so that the server becomes a virtual router for VPN clients.
sudo nano /etc/ufw/before6.rules
By default, there are some rules for the filter
table. Add the following lines at the end of this file. Replace ens3
with your own network interface name. In Nano text editor, you can go to the end of the file by pressing Ctrl+W
, then Ctrl+V
.
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o ens3 -j MASQUERADE
# End each table with the 'COMMIT' line or these rules won't be processed
COMMIT
By default, UFW forbids packet forwarding. We can allow forwarding for our private IPv6 network. Find the ufw6-before-forward
chain in this file and add the following 3 lines, which will accept packet forwarding if the source IP or destination IP is in the fda9:4efe:7e3b:03ea::/48
range.
# allow forwarding for VPN -A ufw6-before-forward -s fda9:4efe:7e3b:03ea::/48 -j ACCEPT -A ufw6-before-forward -d fda9:4efe:7e3b:03ea::/48 -j ACCEPT
Save and close the file. We also need to allow IPv6 VPN clients in the firewall’s INPUT chain.
sudo ufw allow in from fda9:4efe:7e3b:03ea::/48
Restart UFW for the change to take effect.
sudo systemctl restart ufw
Now if you list the rules in the POSTROUTING chain of the NAT table by using the following command:
sudo ip6tables -t nat -L POSTROUTING
You can see the Masquerade rule.
Disconnect the current VPN connection, add an AAAA record for vpn.example.com
and re-establish VPN connection. Then go to https://test-ipv6.com/ to check your IPv6 connectivity.
Set Up IPv6 in Firewall (CentOS)
Enable masquerading for IPv6.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="fda9:4efe:7e3b:03ea::/48" masquerade'
Allow VPN clients in the INPUT chain.
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="fda9:4efe:7e3b:03ea::/48" accept'
Reload firewalld for the changes to take effect.
sudo systemctl reload firewalld
Configure IPv6 in BIND Resolver
If you run your own BIND DNS resolver on the VPN server, you can add the following line in /etc/ocserv/ocserv.conf
file to set the VPN server as the DNS resolver for VPN clients.
dns = fda9:4efe:7e3b::1
Save and close the file. To query DNS names in IPv6, we need to configure BIND to allow IPv6 VPN clients.
Debian/Ubuntu
sudo nano /etc/bind/named.conf.options
Find the allow-recursion
parameter and change it to:
allow-recursion { 127.0.0.1; 10.10.10.0/24; fda9:4efe:7e3b:03ea::/48; };
Save and close the file. Restart BIND9.
sudo systemctl restart bind9
CentOS
sudo nano /etc/named.conf
Find the allow-query
parameter and change it to:
allow-query { 127.0.0.1; 10.10.10.0/24; fda9:4efe:7e3b:03ea::/48; };
Save and close the file. Restart BIND9.
sudo systemctl restart named
Set Up IPv6 in HAProxy
Edit the HAProxy configuration file.
sudo nano /etc/haproxy/haproxy.cfg
Make the https
frontend listen on both IPv4 and IPv6 addresses. Obviously you need to use your own server’s public IPv6 address.
frontend https bind 12.34.56.78:443 bind 2607:f8b0:4006:810::200e:443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 }
Then find the ocserv
backend and add an IPv6 server.
backend ocserv mode tcp option ssl-hello-chk server ocserv 127.0.0.1:443 send-proxy-v2 server ocserv6 [::1]:443 send-proxy-v2
Save and close the file.
To make ocserv listen on both 127.0.0.1
and ::1
, edit the /etc/hosts
file.
sudo nano /etc/hosts
Edit the entry for 127.0.0.1
and ::1
like below, so the vpn.example.com
hostname can be resolved to both addresses.
127.0.0.1 localhost vpn.example.com ::1 ip6-localhost ip6-loopback vpn.example.com
Save and close the file. Then edit ocserv configuration file.
sudo nano /etc/ocserv/ocserv.conf
Find the following line.
listen-host = 127.0.0.1
Change it to
listen-host = vpn.example.com
ocserv will find the IPv4 and IPv6 addresses of vpn.example.com
in the /etc/hosts
file and bind to both 127.0.0.1
and ::1
addresses. Save and close the file. Then restart ocserv and HAProxy
sudo systemctl restart ocserv sudo systemctl restart haproxy
Now run the following command to check the listening status of ocserv. You will see that it’s listening on both 127.0.0.1
and ::1
.
sudo ss -lnpt | grep ocserv
Testing IPv6 Connectivity
Restart your VPN client and go to https://test-ipv6.com/ to check your IPv6 connectivity. If everything goes well, you should see your VPN server’s IPv4 and IPv6 addresses in the test result. And the warning “VPN is only protecting one protocol” should be gone.
If you don’t see your VPN server’s IPv6 address in the test result, perhaps you need to reboot the VPN clients and re-establish VPN connection.
Note: The VPN client doesn’t have to have a public IPv6 address. It can use IPv6 over the IPv4 VPN tunnel.
Wrapping Up
I hope this tutorial helped you run OpenConnect VPN server and Apache/Nginx on the same box. As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂
What is the usecase of OpenConnect AND Webserver on the same box?
I expected to read in this post, why I would need that.
Thank you.
OpenConnect allows you to run your own VPN server in the cloud. Some folks might also want to build a website using Apache or Nginx. Running a VPN server and web sersver on the same box can save some money, because you don’t have to spin up another cloud server.
nice 🙂
Dismissing the issue of ‘money’ and ‘saving some money’; the setup that you wrote is about:
– Two things jumbled into one box [to same money]; or is it
– A webserver, that is also connecting to the outside world via the built in VPN; and offering anonymity for the websites that are within the same ‘box’ ?
Thanx
The first one.
Thank you Xiao for the reply, and of course for the article. I am looking for instructions or how to have a ‘box’ that contains both a Webserver and a VPN services, where the websites IP Address are of the VPN’s IP Address (or anonymized). I don’t have the technical knowledge to know if this is possible or not (that a VPN separate server must be there to accomplish this).
Atlas,
To be clear that I understand, are you trying to run websites from behind a VPN? If that is what you are asking, yes it is possible. In that scenario, only those you grant VPN access to would be able to access you websites. Am I understanding your use case correctly?
If the IPv6 test result says your DNS server can’t access IPv6, that’s probably because you disabled IPv6 in the BIND resolver.
This XML file does not appear to have any style information associated with it. The document tree is shown below.
0.1(1)
Please enter your username.
whats that, on browser i see that, how fix this?
This is not an error. You are not supposed to establish OpenConnect VPN connection inside a web browser. Ignore it and use a standard VPN client.
Hello Xiao,
I deployed ocserv latest version as a container on my server and got domain certificate from Letsencrypt. If I only expose the port of ocserv container and connect to ocserv directly, everything works fine.
However, if I add haproxy as proxy and set `listen-proxy-proto = true` in ocserv. I’ll get the error `worker[]: worker-vpn.c:1544: error parsing CSTP data`, and the client start trying reconnect and server just close the session again and again.
Could you have a look about my HAProxy config file? Maybe you have some ideas. I searched for a day, but haven’t got any answer yet.
Thanks.
“`
defaluts
mode tcp
timeout connect 5s
timeout client 300s
timeout server 300s
frontend tls-in
bind :443 tfo ssl crt /etc/ssl/certs/priv-fullchain-bundle.pem
tcp-request inspect-delay 5s
default_backend ocserv
backend ocserv
server ocserv ocserv:443 send-proxy-v2 ssl verify none
“`
Sorry about the format, I’ll try repost the info below.
Ocserv error:
HAProxy configuration:
My problem has been solved, the answer is to delete the certificate from haproxy configuration, and no need to set
also, just leave the certificate verification to ocserv
在CentOS7上尝试失败了
Hello i have Ubuntu 2004 openvz how can install openconnect vpn can you help me