How to Host a Django ASGI Server on a VPS

Bisesh AdhikariBisesh Adhikari
16 min read

Hosting a Django ASGI server on a VPS can seem daunting due to the complexity of setting up production environments, configuring server tools, and managing security. Many developers struggle with ensuring the server handles requests properly, setting up async support, and managing the right dependencies.

But don’t worry — in this guide, we’ll walk you through the entire process step-by-step.

How to Securely Set Up SSH Access and User Management on a VPS Server

When setting up a VPS server (whether on DigitalOcean, AWS, or any other provider), it is essential to configure it securely to protect your server from unauthorized access. In this blog, we will walk through the steps to:

  1. Set up SSH access to your VPS

  2. Create a non-root user for managing your server

  3. Grant the user sudo privileges

  4. Secure the SSH configuration by disabling root login and password authentication

This process is crucial for ensuring that your server remains safe from brute-force attacks and that only authorized users can perform administrative tasks.

Step 1: SSH Access to Your VPS

The first step in securing your VPS is to ensure you can access it via SSH (Secure Shell) using a secure method. This will allow you to interact with the server remotely.

1.1 Connect to Your VPS

After creating your VPS instance (whether it's on DigitalOcean, AWS, or any VPS provider), you’ll typically be provided with an IP address and the ability to either use SSH keys or password-based authentication. In most cases, using SSH keys is the more secure option.

To SSH into your server using your private key:

ssh -i /path/to/your/private_key user@your-vps-ip

#example 
ssh-key % ssh -i /Users/biseshadhikari/ssh-key root@167.71.225.134
  • Replace /path/to/your/private_key with the path to your private key file.

  • Replace user with the correct username (commonly root or ubuntu).

  • Replace your-vps-ip with the actual IP address of your VPS.

Once logged in, you can proceed to set up a new user for better security.

Step 2: Create a New User (appuser)

It is a best practice to avoid logging in as the root user directly. Instead, you should create a new user with limited privileges, granting them only what is necessary.

2.1 Add a New User

Run the following command to create a new user called appuser:

sudo adduser appuser

This will prompt you to enter a password and some additional user information (like Full Name, Room Number, etc.). You can press Enter to skip these optional fields.

2.2 Grant Sudo Privileges to appuser

By default, the appuser will not have permission to run administrative commands. To grant them sudo privileges, you need to add them to the sudo group.

To do this, run:

sudo usermod -aG sudo appuser

This command adds appuser to the sudo group, allowing them to execute commands with administrative privileges using sudo.

2.3 Switch to the New User

To ensure the new user has proper sudo privileges, switch to the appuser account:

su - appuser

Test that appuser can use sudo by running a simple command like sudo apt update:

sudo apt update

You should be prompted to enter the password for appuser, and the command should execute successfully.

Step 3: Disable Root Login and Password Authentication

Now that you have a non-root user with sudo privileges, it's time to enhance security by disabling root login and password-based authentication for SSH. This ensures that only users with valid SSH keys can access your server, reducing the risk of brute-force attacks.

3.1 Disable Root Login via SSH

To disable direct root login, you need to modify the SSH configuration file:

sudo nano /etc/ssh/sshd_config

Find the line that says:

#PermitRootLogin yes

Change it to:

PermitRootLogin no

This will disable root login over SSH, forcing users to log in using a non-root account.

3.2 Disable Password Authentication

Next, we will disable password-based authentication entirely and enforce SSH key authentication. In the same /etc/ssh/sshd_config file, find the line:

#PasswordAuthentication yes

Change it to:

PasswordAuthentication no

This ensures that only SSH keys are allowed for authentication, adding an extra layer of security.

3.3 Restart SSH Service

After making the changes to the SSH configuration, restart the SSH service to apply the changes:

sudo systemctl restart sshd

Step 4: Verify the SSH Configuration

Now that we have made the necessary changes, let's verify everything works as expected.

4.1 Test Root Login

Try to log in to the server as root:

ssh root@167.71.225.134

You should receive a message like:

Permission denied, please try again.

This confirms that root login has been successfully disabled.

4.2 Test Password Authentication

Try to log in using your username (appuser) with the password:

ssh appuser@167.71.225.134

You should get a password prompt. If you try to enter the password, it will fail because we disabled password authentication. The only way to log in should be using the SSH key.

4.3 Test SSH Key Login

Now, log in as appuser using your SSH key:

ssh -i /Users/biseshadhikari/ssh-key root@167.71.225.134

This should allow you to log in without requiring a password, confirming that key-based authentication is working properly.

Step 5: Clone the Django Channels Project and Set Up

At this stage, you have created a user (appuser), configured SSH, and set up your server environment. Now, let's move forward with cloning your Django Channels project to your VPS and setting it up.


5.1 Clone the Project from GitHub

First, navigate to the /home/appuser/ directory where you want to store your project:

cd /home/appuser/

Clone the Django Channels project from the GitHub repository into your appuser's home directory:

git clone https://github.com/yourusername/your-django-channels-project.git

Once cloned, you’ll have the project stored in /home/appuser/your-django-channels-project.


5.2 Set Up the Virtual Environment

To ensure that your Python environment is isolated and that dependencies are installed correctly, you need to create a virtual environment for the project.

  1. Create the virtual environment:
cd /home/appuser/channelproject
python3 -m venv venv

This creates a virtual environment called venv in the project directory.

  1. Activate the virtual environment:
source venv/bin/activate

Once activated, your shell prompt should change to show that you’re working inside the virtual environment.


5.3 Install Dependencies

Now that the virtual environment is set up, you need to install the project dependencies. These dependencies are typically listed in the requirements.txt file, which should be present in the root directory of the project.

Run the following command to install all the required packages:

pip install -r requirements.txt

This command will install Django, Channels, Redis, and any other dependencies specified in the requirements.txt file.


5.4 Configure the Django Project Settings

Now you need to adjust the settings of the Django project to work correctly in a production environment.

  1. Open the settings file:
nano channelproject/project/settings.py
  1. Set Allowed Hosts:

In the settings file, find the ALLOWED_HOSTS setting. This is where you specify which hostnames or IP addresses can access your Django project. You need to add your server’s IP address or domain name here. For example:

ALLOWED_HOSTS = ['167.71.225.233']

This will allow both the IP address and the domain name to access your Django Channels app.

  1. Set up Redis for Django Channels:

Since you're using Django Channels, you’ll need to configure Redis as the channel layer. In the settings.py file, you need to add the following configuration:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

Ensure that Redis is installed and running on the server

5.5 Create the Non-Login User (channeluser) and Group (channelgroup)

In Linux systems, it is a good practice to create dedicated users and groups for each project, especially for non-login users who don’t need to access the system interactively. This helps in improving the security and ensuring that each service or project has a dedicated user.

5.5.1 Create the User channeluser

The user channeluser is created to own the project files and run tasks related to the Django Channels application, but it will not have login access to the server.

sudo useradd -r -M -d /home/appuser -s /bin/false channeluser
  • useradd: Command to create a new user.

  • -r: Creates a system user (non-interactive), not intended for login purposes.

  • -M: Prevents the creation of a home directory for the user.

  • -d /home/appuser: Specifies the directory where this user’s files will be located (/home/appuser).

  • -s /bin/false: Specifies that the user cannot log in to the system (shell access is denied).

  • channeluser: The name of the user being created.

5.5.2 Create the Group channelgroup

The channelgroup group is used to manage access control for multiple users. This group will be responsible for managing access to the project folder.

sudo groupadd channelgroup
  • groupadd: Command to create a new group.

  • channelgroup: The name of the group.


5.6 Add channeluser to channelgroup and Assign Permissions

After creating the user and group, the next step is to assign the user to the group. This allows the user channeluser to inherit the permissions of the group channelgroup.

5.6.1 Add channeluser to channelgroup

sudo usermod -aG channelgroup channeluser
  • usermod: Command to modify a user’s attributes.

  • -aG: This option appends the user to the group without removing the user from other groups.

  • channelgroup: The group that the user is being added to.

  • channeluser: The user being added to the group.

5.6.2 Add www-data to channelgroup

The www-data user is the default user for web servers like Nginx and Apache. By adding www-data to channelgroup, the web server can access the necessary files in the project directory.

sudo usermod -aG channelgroup www-data
  • www-data: The default user for web servers.

  • channelgroup: The group that www-data is being added to.

By adding www-data to channelgroup, you ensure that the web server (e.g., Nginx) has the required access to interact with the Django Channels project.


5.7: Set Ownership of Project Directory (/home/appuser/channelproject)

Now that we have the user and group set up, we need to configure the permissions for the project directory. This will allow the user channeluser to manage the files while allowing www-data and other necessary users to access the files.

5.7.1 Set Ownership of the Project Directory

sudo chown -R channeluser:channelgroup /home/appuser/channelproject
  • chown -R: Changes the ownership of files and directories recursively.

  • channeluser:channelgroup: Specifies that channeluser should own the files and channelgroup is the group associated with the files.

  • /home/appuser/channelproject: The directory that contains the Django Channels project.

This step ensures that channeluser has control over the project files, while channelgroup allows other users (like www-data) to access them.

5.7.2 Set Permissions for the Project Directory

sudo chmod -R 750 /home/appuser/channelproject
  • chmod -R: Modifies the permissions of files and directories recursively.

  • 750: Sets the permissions in the following way:

    • 7 (Owner channeluser): Read, write, and execute permissions. This means that channeluser has full control over the files.

    • 5 (Group channelgroup): Read and execute permissions. This allows members of the group (like www-data and appuser) to read and execute files but not modify them.

    • 0 (Others): No permissions for others. This ensures that no unauthorized user can access the files.

These permissions make sure that:

  • Only channeluser can modify the files.

  • Members of channelgroup (including www-data) can read and execute the files, which is necessary for the web server to serve static files and interact with the Django Channels application.

  • No other users can access the project files.


5.8: Set Permissions for /home/appuser Directory

You also need to set the appropriate permissions for the /home/appuser directory to ensure that channeluser and channelgroup members have access to it.

5.8.1 Set Ownership of /home/appuser

sudo chown -R channeluser:channelgroup /home/appuser
  • chown -R: Changes the ownership of the /home/appuser directory and all its contents recursively.

  • channeluser:channelgroup: This specifies that channeluser is the owner of /home/appuser and that the group channelgroup is associated with it.

This ensures that the user channeluser has control over the /home/appuser directory, and members of channelgroup have access to the files within the directory.

5.8.2 Set Permissions for /home/appuser

sudo chmod -R 750 /home/appuser
  • chmod -R: Modifies the permissions of /home/appuser and its contents recursively.

  • 750: This allows:

    • 7 (Owner channeluser): Read, write, and execute permissions for channeluser to manage the directory and its contents.

    • 5 (Group channelgroup): Read and execute permissions for group members, allowing them to access the directory and files but not modify them.

    • 0 (Others): No permissions for other users to access the directory.

This ensures that only authorized users (those belonging to channelgroup or channeluser) can access the /home/appuser directory and its contents, improving security.


Step 6: Set Up Redis and Daphne

Django Channels requires both Redis and Daphne to run properly.

6.1 Install Redis

To install Redis on your VPS, run the following commands:

sudo apt update
sudo apt install redis-server

Once Redis is installed, start it and enable it to start on boot:

sudo systemctl start redis
sudo systemctl enable redis

Redis is now running on your VPS and can be used by Django Channels for message brokering.

6.2 Install Daphne

Daphne is an ASGI server that runs your Django Channels application. If Daphne isn’t already listed in your project’s requirements.txt, you can install it using pip:

pip install daphne

Once Daphne is installed, you can start the Daphne server to handle WebSocket connections.

6.3 Start the Daphne Server

You can start the Daphne server manually using the following command:

daphne -u /tmp/daphne.sock project.asgi:application
  • The -u /tmp/daphne.sock option creates a Unix socket that Nginx can use to communicate with Daphne.

  • Replace project.asgi:application with the correct path to your ASGI application.

This command starts Daphne and connects it to your Django project.


Step 7: Configure Nginx for Reverse Proxy

Now that Daphne is running and accepting requests, you need to configure Nginx as a reverse proxy to forward HTTP and WebSocket traffic to Daphne.

7.1 Edit Nginx Configuration

Edit the Nginx configuration file to set up the reverse proxy:

sudo nano /etc/nginx/sites-available/default

Add the following configuration:

server {
    listen 80;
    server_name 167.71.225.233 

    # For regular HTTP requests
    location / {
        proxy_pass http://unix:/tmp/daphne.sock;  # Forward traffic to Daphne via Unix socket
        proxy_set_header Host $host;
        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 $scheme;
    }

    # For WebSocket connections
    location /ws/ {
        proxy_pass http://unix:/tmp/daphne.sock;  # Forward WebSocket traffic to Daphne via Unix socket
        proxy_set_header Host $host;
        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 $scheme;

        # WebSocket-specific headers
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }

    # Error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/html;
    }
}

This configuration will forward all HTTP requests to the Daphne server via the Unix socket at /tmp/daphne.sock.

7.2 Restart Nginx

After editing the Nginx configuration file, restart Nginx to apply the changes:

sudo systemctl restart nginx

Step 8: Set Up Daphne to Run on Boot

To ensure that Daphne starts automatically when your server reboots, you can create a systemd service for Daphne.

8.1 Create the Systemd Service

  1. Create a systemd service file for Daphne:
sudo nano /etc/systemd/system/daphne.service
  1. Add the following content:
[Unit]
Description=Daphne ASGI Server for Django Channels Project
After=network.target

[Service]
User=channeluser
Group=channelgroup
WorkingDirectory=/home/appuser/your-django-channels-project
ExecStart=/home/appuser/channelproject/venv/bin/daphne -u /tmp/daphne.sock project.asgi:application
Restart=always

[Install]
WantedBy=multi-user.target
  1. Enable and start the Daphne service:
sudo systemctl daemon-reload
sudo systemctl enable daphne
sudo systemctl start daphne

This will ensure Daphne runs automatically whenever the server is rebooted.

Step 9: Configure NGINX for the .sock File

At this point, Daphne is running and listening on a Unix socket file (/tmp/daphne.sock), but NGINX still needs to be configured to forward HTTP traffic to Daphne and ensure WebSockets work correctly. We will update the NGINX configuration to handle the proxying and proper routing.

9.1 NGINX Configuration

  1. Create or Edit the NGINX Configuration File:

    First, make sure you have a proper NGINX config file that forwards HTTP traffic to Daphne. If you don't already have the file, create it. If you do, you will modify it.

    You should have an NGINX configuration file located in /etc/nginx/sites-available/ for your project. If you haven’t created one, do so now. For example, if your project is called channels, create the config file:

      nano /etc/nginx/sites-available/channels
    
  2. Add NGINX Configuration:

    Add the following configuration to the NGINX file. This configuration will forward requests to Daphne and handle the WebSocket connections on the specified paths.

     server {
         listen 80;
         server_name 167.71.225.233 bisesh.xyz;  # Replace with your server IP and domain name
    
         location / {
             proxy_pass http://unix:/tmp/daphne.sock;  # Proxy HTTP traffic to Daphne using the Unix socket
             proxy_set_header Host $host;
             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 $scheme;
         }
    
         location /ws/ {  # WebSocket route for Django Channels
             proxy_pass http://unix:/tmp/daphne.sock;  # Proxy WebSocket traffic to Daphne
             proxy_http_version 1.1;
             proxy_set_header Upgrade $http_upgrade;
             proxy_set_header Connection 'upgrade';
             proxy_set_header Host $host;
             proxy_cache_bypass $http_upgrade;
         }
    
         error_page 500 502 503 504 /500.html;
         location = /500.html {
             root /var/www/html;
         }
     }
    
    • proxy_pass http://unix:/tmp/daphne.sock;: This line ensures that all HTTP traffic (regular requests) is forwarded to Daphne via the Unix socket at /tmp/daphne.sock.

    • location /ws/: This specific block handles WebSocket connections (which Django Channels uses). It forwards requests that are under the /ws/ path to Daphne while ensuring that the WebSocket upgrade headers are set properly.

  3. Create a Symlink for the NGINX Site:

    To link the configuration file to the NGINX server, you need to create a symbolic link to /etc/nginx/sites-enabled/ from /etc/nginx/sites-available/. This tells NGINX to use your configuration file.

    Run the following command to create the symlink:

     sudo ln -s /etc/nginx/sites-available/channels /etc/nginx/sites-enabled/
    
  4. Test NGINX Configuration:

    Before restarting NGINX, it’s always a good idea to test the configuration to ensure there are no syntax errors.

    Run the following command to test the configuration:

     sudo nginx -t
    

    If the output is successful and says syntax is okay and test is successful, then you can proceed. If there’s an error, check the configuration file for any issues.

  5. Reload NGINX:

    After the configuration file has been tested, reload NGINX to apply the changes:

     sudo systemctl reload nginx
    

    This will make NGINX apply the new configuration and start forwarding traffic to Daphne via the Unix socket.

Step 10: Set Permissions for the .sock File

Now that NGINX is configured to forward traffic to Daphne, we need to make sure that the Unix socket file (/tmp/daphne.sock) is accessible by the necessary users (like channeluser and channelgroup, as well as nginx which is often part of www-data).

10.1 Set Permissions for the .sock File

Daphne will create the socket file at /tmp/daphne.sock, but by default, it might not have the correct permissions for NGINX to access it.

  1. Change the Socket File’s Permissions:

    You can set the correct permissions for the Unix socket by running:

     sudo chmod 770 /tmp/daphne.sock
     sudo chown channeluser:channelgroup /tmp/daphne.sock
    

    This will ensure that:

    • The channeluser (the user Daphne is running as) and channelgroup (the group associated with channeluser) have full access to the socket file.

    • NGINX, which is running under the www-data group (on most servers), will have read and write permissions as well.

  2. Ensure NGINX Can Access the Socket:

    If your NGINX user (www-data) is not part of the channelgroup, add it to the group to allow proper access:

     sudo usermod -aG channelgroup www-data
    

    This command adds the www-data user (which NGINX runs as) to the channelgroup. It ensures that NGINX can access the socket file.

Step 11: Test the Setup

Now that everything is set up, you can test the configuration:

  1. Open your web browser and go to http://167.71.225.233 or http://bisesh.xyz (if you have set up the domain properly).

  2. The application should be up and running, with NGINX proxying requests to Daphne, and WebSockets working through /ws/.

If everything is working correctly, your application should be live and responsive, with NGINX forwarding both regular HTTP and WebSocket traffic to Daphne.

Conclusion

To summarize, we have:

  1. Set up Daphne to run as a systemd service, ensuring it starts on boot.

  2. Configured NGINX to forward both HTTP and WebSocket traffic to Daphne through a Unix socket.

  3. Set the correct permissions on the Unix socket file, ensuring both NGINX and Daphne can access it.

  4. Created the necessary symbolic link for NGINX and reloaded it to apply the changes.

This complete setup ensures that your Django Channels application runs efficiently with NGINX handling the reverse proxying and Daphne serving the WebSocket traffic.

0
Subscribe to my newsletter

Read articles from Bisesh Adhikari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bisesh Adhikari
Bisesh Adhikari

Student | Tech enthusiast | Aspiring software developer