3
votes

Hello guys I am trying to make work my rails app with nginx as a reverse proxy the rails app and nginx are in separate docker containers and works thru docker-compose services. I am until now without any success.

Rails environment production.rb => config.force_ssl = true

my docker-compose file is this:

version: '3'

services: 
  nginx:
    image: reverse_nginx:latest
    ports:
      - '80:80'
    depends_on:
      - web

  web:
    image: production_my_app:latest
    command: rails s -p 3000 -b '0.0.0.0' -e production
    ports:
      - '3000:3000'
    depends_on:
      - postgres
      - redis
    env_file:
      - ...
    restart: always

For build reverse_nginx:latest I use Dockerfile-nginx with this:

# Base image: debian strech
FROM nginx:stable

RUN apt-get update -qq \
  && apt-get install -y ca-certificates

COPY ssl/server.crt /etc/ssl/certs/server.crt
COPY ssl/server.key /etc/ssl/private/server.key

RUN cd /etc/ssl/private \
  && chmod 600 server.* \
  && cd /etc/ssl/certs \
  && chmod 600 server.* \
  && update-ca-certificates --fresh

# establish where Nginx should look for files
ENV RAILS_ROOT /var/www/my_app/public

# Set working directory inside the image
WORKDIR $RAILS_ROOT

# create log directory
RUN mkdir log

# copy over static assets
COPY public .

# copy custom general nginx conf
COPY config/nginx.conf /etc/nginx/nginx.conf

# copy custom conf for my_app to sites-available folder
COPY config/my.app.conf /etc/nginx/sites-available/my.app.conf

# create sites-enabled folder and soft link custom conf for my_app
RUN mkdir -p /etc/nginx/sites-enabled/ \
  && cd /etc/nginx/sites-enabled/ \
  && ln -s /etc/nginx/sites-available/my.app.conf . \
  && ls -la \
  && nginx -c /etc/nginx/nginx.conf -t

EXPOSE 80

CMD [ "nginx", "-g", "daemon off;" ]

The /etc/nginx/nginx.conf file:

user nginx;
worker_processes auto;
worker_rlimit_nofile 1024;

events {
    multi_accept on;
    worker_connections 1024;
}

http {
    # load configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*.conf;

    # MIME
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    charset utf-8;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    log_not_found off;
    types_hash_max_size 2048;
    client_max_body_size 16M;

    # SSL
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # modern configuration
    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
    ssl_prefer_server_ciphers on;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    # resolver_timeout 2s;
}

The /etc/nginx/sites-available/my_app.conf file:

# HTTPS
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name my.app;
  root /var/www/myapp/public;

  ssl_certificate /etc/ssl/certs/server.crt;
  ssl_certificate_key /etc/ssl/private/server.key;

  # index.html fallback
  location / {
    try_files $uri $uri/ /index.html;
  }

  # reverse proxy
  location /var/www/myapp/public {

    # proxy_pass needs to be name of service on docker compose to connect with container?
    proxy_pass http://web:3000;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

# HTTP redirect to HTTPS
server {
  listen 80;
  listen [::]:80;

  server_name .my.app;

  return 301 https://my.app$request_uri;
}

Scenarios:

$ docker-compose up nginx_1 => message (as expected):

nginx_1 | "ssl_stapling" ignored, issuer certificate not found for certificate "/etc/ssl/certs/server.crt"

scenario 1: visit 0.0.0.0 in browser, url changes to https://my.app browser says: ERR_SSL_PROTOCOL_ERROR

scenario 2: visit 0.0.0.0:80 in browser, url changes to https://my.app browser says: ERR_SSL_PROTOCOL_ERROR

scenario 3: visit 0.0.0.0:3000 in browser, url changes to https://0.0.0.0:3000 rails error log HTTPS parse error malformed invalid HTTP format parsing fails, browser says: ERR_SSL_PROTOCOL_ERROR

scenario 4: visit https://0.0.0.0:80, url stays the same, nginx_1 log throw characters: 192.168.0.1 - - [28/Nov/2018:21:39:22 +0000] "\x16\x03\x01\x00\xB5\x01\x00\x00\xB1\x03\x03\xD3@kn" 400 166 "-" "-"

I think that the problem is in proxy_pass directive in my_app.conf but to be honest I do not know what I am doing right now I am stuck. can you help me to understand what is going on? I had never used nginx

1

1 Answers

3
votes

You have a few problems:

  • The SSL cert needs to be found needs to be fixed in order to get rid of the SSL errors

  • The location should refer to the url that you're accessing (this is a regex)

  • nginx doesn't know about docker

  • the server names aren't configured correctly

Proxy pass

To solve these in reverse order, don't proxy pass to http://web, instead pass to the address that you launch the container on: http://0.0.0.0:3000.

Location

Next, it seems like you want to proxy all web traffic that hits the reverse proxy to rails. If that's the case, then make it your only location block and have it be location / { ... }.

Example:

# reverse proxy
location / {
  # pass everything to rails
  proxy_pass http://0.0.0.0:3000;

  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}

SSL

You might need to combine the certs, but I'd first check to make sure that your certs are actually at the path listed in the config

Server name

Nginx will let multiple hosts hit the same IP address and route appropriately. This is based on the server_name directive. It looks like you want all the traffic leading to 0.0.0.0 (or to your eventual host) to be proxied to rails. So, the best thing to do is just add catchall. In both the :80 and :443 server blocks, change server_name my.app; to server_name _ my.app;. There, the _ acts as a wildcard. Without doing that, you would need to edit your /etc/hosts file and point my.app to localhost and then access it by going to https://my.app instead of 0.0.0.0.