└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Setting Up a Secure VPS 2 | 3 | > **Pro tip:** Everywhere you see curly braces in these instructions, for example `{enter ip address here}`, the braces are placeholders indicating that there should a value there. Do not type in the braces. 4 | 5 | ## Prerequisites 6 | 7 | 1. Register a domain name with [Google Domains](https://domains.google.com/about/), [Gandi.net](http://www.gandi.net/) or other registrar. Look at `.com`, and `.me` domains for your personal site. 8 | 2. [Sign up](https://m.do.co/c/47e5e578d1cd) for a DigitalOcean account. You'll get a $50 credit for 30 days, which let's you try all of their services for free for a month. 9 | 10 | ## Getting ready 11 | 12 | 1. Follow the direction from Github's documentation to get yourself a shiny, [new SSH key](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/). 13 | 1. Add your SSH key to your DigitalOcean account. It's under `Settings > Security`. You get to settings from the gear icon in the upper-right corner of the navigation. 14 | 1. While you're there, set up 2FA for your DigitalOcean account. Yeah, I know 2FA seems like overkill... until someone uses a brute force attack to log into your account, change all of your security protocols, installs tons of malware on your domain, and then tries to blackmail you into getting it all back. 15 | 1. Create a Droplet. Select the $5/mo VPS type, accept all the defaults options they provide and **make sure you add your SSH key** by clicking that checkbox near the bottom of the screen. Then click the button to create your VM at the bottom. 16 | 17 | ## Accessing your VPS 18 | 19 | Make sure that your private SSH key is currently loaded into your bash session by typing the following commands into your terminal. 20 | 21 | ```sh 22 | eval "$(ssh-agent -s)" 23 | ssh-add ~/.ssh/id_rsa # If your SSH key name is different, use that 24 | ``` 25 | 26 | In your CLI, execute the command `ssh root@your.droplet.IP.address`. This will open up a secure shell connection to your droplet. 27 | 28 | ## Creating a user account 29 | 30 | Your first step is to create an account for yourself. 31 | 32 | 1. Decide on a username 33 | 1. `mkdir /home/{ username }` 34 | 1. `mkdir /home/{ username }/.ssh` 35 | 1. `touch /home/{ username }/.ssh/authorized_keys` 36 | 1. `useradd { username } --home /home/{ username }` 37 | 1. `passwd { username }` then press enter. 38 | 1. You will be prompted to enter in the password for this account twice 39 | 1. Change the default shell with `chsh -s /bin/bash { username }` 40 | 41 | ### Account security 42 | 43 | #### sudo 44 | 45 | Allow new account to gain administrative privileges. 46 | 47 | ``` 48 | sudo visudo 49 | ``` 50 | 51 | This will open up a file in the Nano file editor, not vim. You can just start editing the file without the need to hit the `i` key. 52 | 53 | Find the section where user privileges are specified. You should see a configuration section like this one. Add your new user account to be able to use `sudo`. 54 | 55 | ``` 56 | # User privilege specification 57 | root ALL=(ALL:ALL) ALL 58 | { username } ALL=(ALL) ALL 59 | ``` 60 | 61 | After you've added that line in... 62 | 63 | 1. Hit `ctrl+x` to exit. 64 | 1. You'll be prompted to save the file, so just hit enter. 65 | 1. Then press `y` to verify the save. 66 | 67 | ### Transferring ownership 68 | 69 | Set the new user as owner of the home directory: `chown -R { username } /home/{ username }` 70 | 71 | #### Adding SSH key 72 | 73 | 1. Open up a new terminal instance so that you have a command line on your local computer. 74 | 1. In your shell, type `cat ~/.ssh/id_rsa.pub`. If you created a different SSH key name, type yours instead of `id_rsa.pub`. 75 | 1. Copy the entire public key that got printed to the shell. 76 | 1. Switch to your remote shell. 77 | 1. `vim /home/{username}/.ssh/authorized_keys` 78 | 1. Press `i`. 79 | 1. Paste in your public key. 80 | 1. Press `esc` 81 | 1. `:x` to save and quit. 82 | 83 | ## Using your account 84 | 85 | 1. Terminate your remote SSH session as root with the `exit` command. 86 | 1. Now create a new SSH session with your new account: `ssh { username }@{ ip address }`. 87 | 88 | You are now logged into your DigitalOcean virtual machine on your user account. You're ready to start installing things. 89 | 90 | ## Base installs 91 | 92 | 1. Run the command `sudo apt-get update` 93 | 1. Install base packages `sudo apt-get install curl wget unzip git ufw nodejs npm nginx` 94 | 95 | ## Firewall 96 | 97 | Now you'll set up ufw (uncomplicated firewall). Enter in the following commands. 98 | 99 | ```bash 100 | sudo ufw default deny incoming 101 | sudo ufw default allow outgoing 102 | sudo ufw allow ssh 103 | sudo ufw allow www 104 | sudo ufw enable 105 | sudo ufw status verbose 106 | ``` 107 | 108 | Your server is now protected by a firewall that will deny **any** traffic other than SSH connections and web traffic. 109 | 110 | # Getting Your Domain Pointed to Your Droplet 111 | 112 | ## Registrar DNS Steps 113 | 114 | Once you have purchased your domain you can set it up to point at the server hosting your website. To do this you will need to tell your registrar where the server is to be resolved. There are three *name servers* that you will need to tell your registrar about. The GUI for each registrar is different, but you will need to enter the following on your registrar's control panel under name servers each on a new line. 115 | 116 | `ns1.digitalocean.com` 117 | `ns2.digitalocean.com` 118 | `ns3.digitalocean.com` 119 | 120 | ## Pointing Web Traffic to Your Droplet 121 | 122 | You will need to create custom records for your droplet on Digital Ocean, so go back to your Digital Ocean account and go to your *Networking* tab on the control panel and create a record there as well. From the tab go to the *Domains* tab and add a domain. To do this you will need to put your domain (i.e. `you.com`) in the domain field and pick a droplet to host it on. 123 | 124 | You'll notice your new domain record will display beneath the creation form. Click on the domain entry and you'll see the DNS record form. 125 | 126 | You'll need to add an **A** record for your domain, which is chosen by default on this form. In the *Enter name* field, enter in the value of **www** and in the *Enter IP Address* field, enter in your Droplet's IP address. 127 | 128 | # Install & Configure Nginx 129 | 130 | Nginx is a powerful web server that will allow you to serve your personal website, and your front-end capstone, from your new VPS. You already have nginx installed if you've followed the steps in this walk-through. 131 | 132 | Digital Ocean has a [wonderful tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04) showing you how to set it up. 133 | 134 | > When you get to step 5 they show the directory where you clone your repo if it is a static personal site. `/var/www/html` 135 | 136 | If you want to serve your Django, Rails, or Node server-side capstone from your VPS, then read below about seting up gunicorn with nginx. 137 | 138 | ## SSL and secure Nginx 139 | 140 | There's a [Digital Ocean tutorial](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04) that walks you through getting an SSL ceritificate from the Let's Encrypt* Certificate Authority. This is required for your web site to be trusted by web browsers, and use the `https` protocol for connections. 141 | 142 | Then it shows you how to set up the Nginx web server to use that certificate. 143 | 144 | # gunicorn and nginx setup for Django app 145 | 146 | For example, if I wanted to deploy a Django REST Framework API project, I would create the following files. 147 | 148 | ## Setup 149 | 150 | 1. Install gunicorn with `sudo apt-get install gunicorn` 151 | 1. Clone your Django project into a sub-directory of your choosing in your home directory. 152 | 1. Copy the path to the project directory using `pwd`. You will need the path below. 153 | 154 | ## systemd gunicorn service 155 | 156 | Create a service file for gunicorn that will keep the service running permanently. 157 | 158 | > /lib/systemd/system/gunicorn.service 159 | 160 | This is an example configuration that you would place in that file. The `WorkingDirectory` value is the directory that contains the entire Django project. 161 | 162 | ``` 163 | [Unit] 164 | Description={yourProject} gunicorn daemon 165 | After=network.target 166 | 167 | [Service] 168 | Environment="DJANGO_SECRET_KEY={use a password generator and place the strong password here}" 169 | Environment="DEBUG=True" 170 | Environment="DEVELOPMENT_MODE=False" 171 | User={your username here} 172 | Group=www-data 173 | 174 | WorkingDirectory=/full/path/to/django/project/directory 175 | # Example: /home/steve/bangazon-api 176 | 177 | ExecStart={path to gunicorn executable} -w 3 --bind 127.0.0.1:8000 {project name}.wsgi 178 | # Example: ExecStart=/home/steve/.local/share/virtualenvs/bangazon-api-23yxXEvw/bin/gunicorn -w 3 --bind 127.0.0.1:8000 BangazonPlatform.wsgi 179 | 180 | PrivateTmp=true 181 | 182 | [Install] 183 | WantedBy=multi-user.target 184 | ``` 185 | 186 | ## nginx config for API 187 | 188 | Next, you configure nginx to route requests coming in on a sub-domain to the Django application. Create the following file. 189 | 190 | > /etc/nginx/sites-available/api 191 | 192 | It would contain the following configuration. You would replace the `server_name` value with the sub-domain that you created with an `A` record for your domain. 193 | 194 | ``` 195 | server { 196 | listen 80; 197 | server_name api.replacewithyourdomain.com; 198 | access_log /var/log/nginx/api.log; 199 | 200 | location / { 201 | proxy_set_header X-Real-IP $remote_addr; 202 | proxy_set_header X-NginX-Proxy true; 203 | 204 | include proxy_params; 205 | proxy_pass http://127.0.0.1:8000; 206 | } 207 | } 208 | ``` 209 | 210 | > **Pro tip:** Make sure you make a symlink of the file in **sites-available** to the corresponding file in **sites-enabled**. 211 | > 212 | > `ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/api` 213 | 214 | ## Starting Your Django Project Service 215 | 216 | Since you added a new service, you need to run this command. If you ever modify the `gunicorn.service` file, you need to run it again. 217 | 218 | ```sh 219 | sudo systemctl daemon-reload 220 | ``` 221 | 222 | Lastly, start the service that uses gunicorn to run your application. 223 | 224 | ```sh 225 | sudo service gunicorn start 226 | ``` 227 | 228 | To see if it launched correctly, run the following command. 229 | 230 | ```sh 231 | sudo journalctl -f -u gunicorn 232 | ``` 233 | 234 | If everything worked, you should see three lines at the end of the file that have something like "Booting worker with pid: 000000" in them. 235 | 236 | ```sh 237 | Nov 28 00:17:36 gunicorn[38735]: [2021-11-28 00:17:36 +0000] [38735] [INFO] Starting gunicorn 20.1.0 238 | Nov 28 00:17:36 gunicorn[38735]: [2021-11-28 00:17:36 +0000] [38735] [INFO] Listening at: http://127.0.0.1:8000 (38735) 239 | Nov 28 00:17:36 gunicorn[38735]: [2021-11-28 00:17:36 +0000] [38735] [INFO] Using worker: sync 240 | Nov 28 00:17:36 gunicorn[38761]: [2021-11-28 00:17:36 +0000] [38761] [INFO] Booting worker with pid: 38761 241 | Nov 28 00:17:36 gunicorn[38762]: [2021-11-28 00:17:36 +0000] [38762] [INFO] Booting worker with pid: 38762 242 | Nov 28 00:17:36 gunicorn[38763]: [2021-11-28 00:17:36 +0000] [38763] [INFO] Booting worker with pid: 38763 243 | ``` 244 | 245 | If you don't see this, ask for help. 246 | --------------------------------------------------------------------------------