I am trying to use DigitalOcean VPS as a openVPN server to access services (e.g. nextcloud) hosted on my home network through subdomains (e.g. nextcloud.example.com).
I have set up the following:
I have tried to add a virtual_host file for nextcloud.example.com to nginx-proxy routing request to the openvpn port 3000, and then within the openvpn container using iptables to forward all request on port 3000 to the internal nextcloud IP.
Would really appreciate any help as I am to be honest a bit in the deep end here?
kylemanna/openvpn - iptables config forwarding
user@Debianwebhost:~$ docker exec -it vpn bash
bash-4.4# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:3000 to:192.168.0.99:80
DNAT udp -- anywhere anywhere udp dpt:3000 to:192.168.0.99:80
DNAT udp -- anywhere anywhere udp dpt:3000 to:192.168.0.99:80
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
SNAT tcp -- anywhere 192.168.0.99 tcp dpt:http to:172.17.0.2:3000
SNAT udp -- anywhere 192.168.0.99 udp dpt:http to:172.17.0.2:3000
nginx-proxy virtual host config
user@Debianwebhost:/etc/nginx/vhost.d$ cat nextcloud.example.com
server {
server_name _; # This is just an invalid value which will never trigger on a real hostname.
listen 80;
access_log /var/log/nginx/access.log vhost;
return 503;
}
# nextcloud.example.com
upstream nextcloud.example.com {
## Can be connect with "bridge" network
# vpn
server 172.17.0.2:3000;
}
server {
server_name nextcloud.example.com;
listen 80 ;
access_log /var/log/nginx/access.log vhost;
location / {
proxy_pass http://nextcloud.example.com;
}
}
nginx-proxy nginx.conf
user@Debianwebhost:/etc/nginx$ cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
nginx-proxy default.conf
user@Debianwebhost:/etc/nginx/conf.d$ cat default.conf
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
default $http_x_forwarded_port;
'' $server_port;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
default upgrade;
'' close;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header
map $scheme $proxy_x_forwarded_ssl {
default off;
https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log off;
resolver [hidden ips, but there are 2 of them];
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
server_name _; # This is just an invalid value which will never trigger on a real hostname.
listen 80;
access_log /var/log/nginx/access.log vhost;
return 503;
}
# nextcloud.example.com
upstream nextcloud.example.com {
## Can be connect with "bridge" network
# vpn
server 172.17.0.2:3000;
}
server {
server_name nextcloud.example.com;
listen 80 ;
access_log /var/log/nginx/access.log vhost;
location / {
proxy_pass http://nextcloud.example.com;
}
}
Hello, @Svarto , you have this issue closed, does that mean you've got a success on that? I have similar setup and want to get an access to we service hosted on VPN client using public domain.
I use haproxy container and kylemanna openvpn container. Could you help me out?
Sure, how far along are you? Have you tried what I wrote in SuperUser on the same issue: https://superuser.com/a/1302909/869159
Let me know if that works?
Actually I have something like that 
And when I request my public domain assigned to 155.55.55.55 it follows trough the host machine to haproxy container. Haproxy service move request to the 192.168.20.10 (OpenVPN server container) and... finish.
I suppose taht I have to configure something and somehow inside OpenVPN service container which is based on kylemanna/docker-openvpn docker image. But how? Have no idea.
Btw. if I request 10.10.0.2 from within openvpn-server container everything works well. I know it because I tried to run curl -Lv 10.10.0.2 after docker exec -it openvpn-server bash and I have response from haproxy on the appliance painted on the right.
So, that is why I think that my issue is hidden inside openvpn-server container.
Just in case, the result of # iptables -t nat -L from within openvpn-server container is:
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER_OUTPUT all -- anywhere localhost
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
DOCKER_POSTROUTING all -- anywhere localhost
MASQUERADE all -- 10.10.0.0/24 anywhere
Chain DOCKER_OUTPUT (1 references)
target prot opt source destination
DNAT tcp -- anywhere localhost tcp dpt:domain to:127.0.0.11:44269
DNAT udp -- anywhere localhost udp dpt:domain to:127.0.0.11:60479
Chain DOCKER_POSTROUTING (1 references)
target prot opt source destination
SNAT tcp -- localhost anywhere tcp spt:44269 to::53
SNAT udp -- localhost anywhere udp spt:60479 to::53
but I don't understand what does it say because of my knowledge of routing is too poor
What are your ip routes for the machine / VPS you have on the left in your diagram?
The curl -Lv 10.10.0.2 does not actually check if you can reach the haproxy, it only checks if it can reach the client inside the VPN. You have still not confirmed that your requests are routed to the proper subnet through the client.
Have you set any routes within openvpn, i.e. could you please post your server configuration file? It is found under etc/openvpn/openvpn.conf inside the docker container.
This is my docker-compose.yml
version: "3"
services:
ovpn:
image: kylemanna/openvpn
container_name: ovpn
privileged: true
ports:
- "1194:1194/udp"
volumes:
- ./ovpn-server/conf:/etc/openvpn
devices:
- /dev/net/tun
cap_add:
- NET_ADMIN
networks:
home-vpn:
aliases:
- home
extra_hosts:
- home:10.10.0.10
restart: unless-stopped
haproxy:
image: haproxy:latest
container_name: haproxy
depends_on:
- ovpn
ports:
- 443:443
- 80:80
volumes:
- ./haproxy:/usr/local/etc/haproxy
networks:
home-vpn:
restart: unless-stopped
networks:
home-vpn:
This is haproxy.cnf
daemon
maxconn 2048
tune.ssl.default-dh-param 2048
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Default ciphers to use on SSL-enabled listening sockets.
# For more information, see ciphers(1SSL). This list is from:
# https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
# An alternative list with additional directives can be obtained from
# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
defaults
mode http
timeout connect 5000ms
timeout client 5000ms
timeout server 5000ms
option forwardfor
option http-server-close
frontend home-http
bind *:80
reqadd X-Forwarded-Proto:\ http
default_backend home-backend
frontend home-https
bind *:443 ssl crt /usr/local/etc/haproxy/certs/home.domain.com.pem
reqadd X-Forwarded-Proto:\ https
default_backend home-backend
backend home-backend
redirect scheme https if !{ ssl_fc }
server home.domain.com home check
This is openvpn.conf
server 10.10.0.0 255.255.255.0
verb 3
key /etc/openvpn/pki/private/home.domain.com.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/home.domain.com.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key
key-direction 0
keepalive 10 60
persist-key
persist-tun
proto udp
port 1194
dev tun0
status /tmp/openvpn-status.log
topology subnet
user nobody
group nogroup
comp-lzo no
client-to-client
client-config-dir ccd
### Route Configurations Below
route 10.10.0.0 255.255.255.0
push "comp-lzo no"
And the home client specific configuration placed in ccd directory:
ifconfig-push 10.10.0.10 255.255.255.0
Just in case, I found solution several month ago. And it works fine and pretty stable.
This is how it looks like:
version: "3"
services:
nginx:
image: nginx:alpine
container_name: nginx
depends_on:
- ovpn
network_mode: "service:ovpn"
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/:ro
- ./nginx/certs/:/etc/nginx/certs/:ro
- ./nginx/services.d/:/etc/nginx/services.d/:ro
restart: unless-stopped
ovpn:
image: kylemanna/openvpn
container_name: ovpn
privileged: true
ports:
- 1194:1194/udp
- 443:443
- 80:80
volumes:
- ./ovpn-server/conf:/etc/openvpn
devices:
- /dev/net/tun
cap_add:
- NET_ADMIN
extra_hosts:
- server.one:10.0.0.33
- server.two:10.0.0.34
restart: unless-stopped
# WebSockets support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name domain.com www.domain.com;
include /etc/nginx/services.d/domain.com.conf;
}
server {
listen 80;
listen [::]:80;
server_name one.domain.com *.one.domain.com;
include /etc/nginx/services.d/one.domain.com.conf;
}
server {
listen 80;
listen [::]:80;
server_name two.domain.com *.two.domain.com;
include /etc/nginx/services.d/two.domain.com.conf;
}
server 10.0.0.0 255.255.255.0
verb 3
key /etc/openvpn/pki/private/sycret.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/sycret.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key
key-direction 0
keepalive 10 60
persist-key
persist-tun
proto udp
port 1194
dev tun0
status /tmp/openvpn-status.log
topology subnet
user nobody
group nogroup
comp-lzo no
comp-noadapt
client-to-client
client-config-dir ccd
### Route Configurations Below
route 10.0.0.0 255.255.255.0
ifconfig-push 10.0.0.33 255.255.255.0
ifconfig-push 10.0.0.34 255.255.255.0
Thank you for taking the time to post the solution.
I will try it soon and give you a feedback...
Thanks
Most helpful comment
Just in case, I found solution several month ago. And it works fine and pretty stable.
This is how it looks like:
docker-compose.yml
nginx/services.conf
ovpn-server/conf/openvpn.conf
ovpn-server/conf/ccd/server.one
ovpn-server/conf/ccd/server.two