title: Reverse-proxying: Flask app with Bokeh server on Nginx
body: <p>I have created a website with Flask that is serving a Bokeh app on a Digital Ocean VPN. Everything worked fine until I secured the server with Let's Encrypt following <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04" rel="nofollow noreferrer">this tutorial</a>.</p>
  
  <p>In step 3 of the tutorial the Nginx configuration file is changed, which might be the crux of the problem I'm getting:</p>
  
  <p>When I go on the website, the Flask content is rendered perfectly. However, the Bokeh app is not running. In the Inspection Console I get the following Error (note that I hashed out the IP address of my website):</p>
  
  <pre><code>Mixed Content: The page at 'https://example.com/company_abc/' was loaded over HTTPS, 
  but requested an insecure script 
  'http://###.###.###.##:5006/company_abc/autoload.js?bokeh-autoload-element=f…aab19c633c95&amp;bokeh-session-id=AvWhaYqOzsX0GZPOjTS5LX2M7Z6arzsBFBxCjb0Up2xP'. 
  This request has been blocked; the content must be served over HTTPS.
  </code></pre>
  
  <p>I understand that I might have to use a method called reverse proxying, which is described <a href="http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#reverse-proxying-with-nginx-and-ssl" rel="nofollow noreferrer">here</a>. However, I wasn't able to get it to work. </p>
  
  <p>Does anybody have an idea how to solve this? A similar problem was described <a href="https://stackoverflow.com/questions/38081389/bokeh-server-reverse-proxying-with-nginx-gives-404/38505205#38505205">here</a>.</p>
  
  <p>Here are my modified server files:</p>
  
  <p>'/etc/nginx/sites-available/default':</p>
  
  <pre><code>upstream flask_siti {
          server 127.0.0.1:8118 fail_timeout=0;
  }
  server {
          listen 443 ssl;
  
          server_name example.com www.example.com;
  
          ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
          ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
          ssl_prefer_server_ciphers on;
          ssl_dhparam /etc/ssl/certs/dhparam.pem;
          ssl_ciphers '###';
          ssl_session_timeout 1d;
          ssl_session_cache shared:SSL:50m;
          ssl_stapling on;
          ssl_stapling_verify on;
          add_header Strict-Transport-Security max-age=15768000;
  
          charset utf-8;
          client_max_body_size 75M;
  
          access_log /var/log/nginx/flask/access.log;
          error_log  /var/log/nginx/flask/error.log;
  
          keepalive_timeout 5;
  
          location / {
                  # checks for static file, if not found proxy to the app
                  try_files $uri @proxy_to_app;
          }
  
          location @proxy_to_app {
                  proxy_redirect off;
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_pass http://flask_siti;
          }
  }
  
  server {
      listen 80;
      server_name example.com www.example.com;
      return 301 https://$host$request_uri;
  }
  </code></pre>
  
  <p>'/etc/supervisor/conf.d/bokeh_serve.conf':</p>
  
  <pre><code>[program:bokeh_serve]
  command=/opt/envs/virtual/bin/bokeh serve company_abc.py company_xyz.py --allow-websocket-origin=www.example.com --allow-websocket-origin=example.com --host=###.###.###.##:5006 --use-xheaders
  directory=/opt/webapps/flask_telemetry
  autostart=false
  autorestart=true
  startretries=3
  user=nobody
  </code></pre>
  
  <p>'/etc/supervisor/conf.d/flask.conf'</p>
  
  <pre><code>[program:flask]
  command=/opt/envs/virtual/bin/gunicorn -b :8118 website_app:app
  directory=/opt/webapps/flask_telemetry
  user=nobody
  autostart=true
  autorestart=true
  redirect_stderr=true
  </code></pre>
  
  <p>And here is my Flask app (Note that I hashed out security related info):</p>
  
  <pre><code>from flask import Flask
  from flask_sqlalchemy import SQLAlchemy
  from flask import render_template, request, redirect, url_for
  from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required, roles_accepted, current_user
  from flask_security.decorators import anonymous_user_required
  from flask_security.forms import LoginForm
  from bokeh.embed import autoload_server
  from bokeh.client import pull_session
  from wtforms import StringField
  from wtforms.validators import InputRequired
  from werkzeug.contrib.fixers import ProxyFix
  
  app = Flask(__name__)
  app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://###:###@localhost/telemetry'
  app.config['SECRET_KEY'] = '###'
  app.config['SECURITY_REGISTERABLE'] = True
  app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'
  app.config['SECURITY_POST_LOGIN_VIEW'] = '/re_direct'
  app.debug = True
  db = SQLAlchemy(app)
  
  # Define models
  roles_users = db.Table('roles_users',
          db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
          db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
  
  class Role(db.Model, RoleMixin):
      id = db.Column(db.Integer(), primary_key=True)
      name = db.Column(db.String(80), unique=True)
      description = db.Column(db.String(255))
  
  class User(db.Model, UserMixin):
      id = db.Column(db.Integer, primary_key=True)
      username = db.Column(db.String(255), unique=True)
      password = db.Column(db.String(255))
      active = db.Column(db.Boolean())
      confirmed_at = db.Column(db.DateTime())
      roles = db.relationship('Role', secondary=roles_users,
                              backref=db.backref('users', lazy='dynamic'))
  
  class ExtendedLoginForm(LoginForm):
      email = StringField('Username', [InputRequired()])
  
  # Setup Flask-Security
  user_datastore = SQLAlchemyUserDatastore(db, User, Role)
  security = Security(app, user_datastore, login_form=ExtendedLoginForm)
  
  # Views
  @app.route('/')
  @anonymous_user_required
  def index():
      return render_template('index.html')
  
  @app.route('/re_direct/')
  @login_required
  def re_direct():
      identifier = current_user.username
      print(identifier)
      return redirect(url_for(identifier))
  
  @app.route('/index/')
  @login_required
  @roles_accepted('admin')
  def admin():
      return render_template('admin.html')
  
  @app.route("/company_abc/")
  @login_required
  @roles_accepted('company_abc', 'admin')
  def company_abc():
      url='http://###.###.###.##:5006'
      session=pull_session(url=url,app_path="/company_abc")
      bokeh_script=autoload_server(None,app_path="/company_abc",session_id=session.id,url=url)
      return render_template("company_abc.html", bokeh_script=bokeh_script)
  
  app.wsgi_app = ProxyFix(app.wsgi_app)
  
  if __name__ == '__main__':
      app.run()
  </code></pre>