├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Bandt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setting up Ubuntu Server for running a single Website with Node.js and MongoDB 2 | 3 | This little guide shows how to set up an Ubuntu Server that is dedicated to run a single website with Node.js and MongoDB. If you are looking for a more generic solution to run multiple websites on a single server, take a look at the [Node.js Web Server Guide](https://github.com/aspnetde/nodejs-webserver-guide). It provides some more details to security aspects which don't matter if there is only one application running. 4 | 5 | ## Create your Droplet (DigitalOcean only) 6 | 7 | I won't tell you how to create a Droplet, because it seems self-explaining to me. If you need any help with this, this little tutorial isn't the thing you should read anyway, at least yet ;-). 8 | 9 | ## Create a User called www 10 | 11 | You could run all your stuff as root, but I don't think that's a good idea. So connect to your only just created server and log in via root: 12 | 13 | ssh root@{ip-address} 14 | 15 | Next, create a the www user: 16 | 17 | adduser www 18 | 19 | Now provide root privilige, (Other than the root account the www user won't run with these priviliges all the time, but it could when requested, what will be necessary at least during the installation process.) 20 | 21 | Call `visudo` and add the following line right below the root's line: 22 | 23 | www ALL=(ALL:ALL) ALL 24 | 25 | Now `exit` your ssh connection and re-connect as www. 26 | 27 | ## Set up SSH Key for www 28 | 29 | cat ~/.ssh/id_rsa.pub | ssh www@{ip-address} "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" 30 | 31 | 32 | At this point you should be requested to provide the password of the www user at login for the last time. `exit` and reconnect – now you should be authenticating via SSH Key. 33 | 34 | ssh www@{ip-address} 35 | 36 | ## Install the required software 37 | 38 | ### Make Tools 39 | 40 | The make tools are essential to build some npm packages and other stuff. So it’s generally a good idea to install them early. 41 | 42 | sudo apt-get install gcc make build-essential 43 | 44 | (If the installation of build-essential fails, see Misc/Missing packages section). 45 | 46 | ### nginx 47 | 48 | sudo apt-get install nginx 49 | 50 | Once the setup of nginx is complete, you should be able to call http://{server_ip} and see the default page with the “Welcome to nginx!” headline. 51 | 52 | Also make sure the server starts automatically after booting the system (Should be enabled by default): 53 | 54 | sudo update-rc.d nginx defaults 55 | 56 | ### Node.js 57 | 58 | If not installed with the initial creation of your droplet (DigitalOcean only; workes just fine!), use this: 59 | 60 | wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.14.0/install.sh | bash 61 | 62 | # Refresh Path 63 | source ~/.profile 64 | 65 | # Use latest Node.JS version 66 | nvm install v0.11.13 67 | 68 | # Make it default 69 | nvm use default v0.11.13 70 | 71 | ### Bower 72 | 73 | sudo npm install bower -g 74 | 75 | ### PM2 76 | 77 | PM2 helps to run the node application by logging errors, restarting after crashing etc. 78 | 79 | sudo npm install pm2 -g 80 | 81 | ### Glances 82 | 83 | Glances can be used to monitor the overall state of the server. 84 | 85 | sudo apt-get install python-pip build-essential python-dev 86 | sudo pip install Glances 87 | sudo pip install PySensors 88 | 89 | ### Git 90 | 91 | sudo apt-get install git 92 | 93 | ### Zip 94 | 95 | sudo apt-get install zip 96 | 97 | ### MongoDB 98 | 99 | For a detailed explanation see [http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/](http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/). 100 | 101 | Install the database service: 102 | 103 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 104 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list 105 | sudo apt-get update 106 | sudo apt-get install -y mongodb-org 107 | 108 | Pin the current version: 109 | 110 | echo "mongodb-org hold" | sudo dpkg --set-selections 111 | echo "mongodb-org-server hold" | sudo dpkg --set-selections 112 | echo "mongodb-org-shell hold" | sudo dpkg --set-selections 113 | echo "mongodb-org-mongos hold" | sudo dpkg --set-selections 114 | echo "mongodb-org-tools hold" | sudo dpkg --set-selections 115 | 116 | ### Add the website’s directories 117 | 118 | Websites are organized as follows: 119 | 120 | Directory | Path 121 | ------------ | ------------- 122 | Root | /var/www 123 | Git repository | /var/www/repo 124 | Website root | /var/www/www 125 | 126 | cd /var 127 | sudo mkdir www 128 | sudo chown www www 129 | cd www 130 | mkdir repo && mkdir www 131 | 132 | ### Create a Git repository 133 | 134 | In `/var/www/repo` run 135 | 136 | git init --bare 137 | 138 | ### Add the Git deployment hook 139 | 140 | This hook is used to deploy changes made to the master repository. It can be customized for each website depending on the specific needs. 141 | 142 | Go to `/var/www/repo/hooks` and create a new file called “post-receive”: 143 | 144 | vi post-receive 145 | 146 | Add the following commands to it: 147 | 148 | #!/bin/bash 149 | 150 | # Make this executable: chmod +x post-receive 151 | 152 | PREPARATION_DIR="/var/www/repo/$(uuidgen)" 153 | WEBSITE_ROOT="/var/www/www" 154 | PM2_APP_NAME="website-com" 155 | 156 | echo "Deployment started" 157 | 158 | read oldrev newrev branch 159 | 160 | if [[ $branch =~ .*/master$ ]]; 161 | then 162 | echo "Master received. Deploying to production..." 163 | 164 | # Creates a temporary working directory 165 | mkdir $PREPARATION_DIR 166 | 167 | # Checks out the master from the repository 168 | GIT_WORK_TREE="$PREPARATION_DIR" git checkout -f 169 | 170 | # Installing all npm and bower modules/packages 171 | cd $PREPARATION_DIR 172 | npm install 173 | bower install 174 | 175 | # Removes all files in the Website's root 176 | cd $WEBSITE_ROOT 177 | rm -rf * 178 | 179 | # Copies all files over 180 | cd $PREPARATION_DIR 181 | cp -r . $WEBSITE_ROOT 182 | 183 | # Restart the Website via PM2 184 | pm2 restart $PM2_APP_NAME 185 | 186 | # Removes the preparation directory 187 | rm -R $PREPARATION_DIR 188 | else 189 | echo "$branch successfully received. Nothing to do: only the master branch may be deployed on this server." 190 | fi 191 | 192 | echo "Deployment finished" 193 | 194 | Remember the value of **PM2_APP_NAME** and use it as an identifier for your pm2 application later. 195 | 196 | After saving, make the script executable: 197 | 198 | chmod +x post-receive 199 | 200 | ### Push your application to your repository 201 | 202 | By cloning your deployment repository on your local development machine and pushing the first version to the master branch, you should receive a fresh version at `/var/www/www` (Check with `ls –l`). 203 | 204 | git clone ssh://{username}@{ipaddress}/var/www/repo website-com 205 | 206 | Make sure the user you’re connecting with has the necessary rights to run the Git repository. It’s recommended to connect with the user you have just created before to run the website, because he/she has the necessary access rights. 207 | 208 | ### Set up PM2 209 | 210 | #### Run the startup script 211 | 212 | To start pm2 with the system: 213 | 214 | pm2 startup ubuntu 215 | 216 | PM2 will tell you, you have to run this command as root, and print the full command to execute, for example: 217 | 218 | sudo env PATH=$PATH:/usr/local/bin pm2 startup ubuntu -u www 219 | 220 | Run it :-). 221 | 222 | #### Start your application 223 | 224 | cd /var/www/website-com/www/ 225 | pm2 start app.js --name "website-com" 226 | 227 | If everything works PM2 reponds with `Process {nameofstarting.js}` launched. Wait a few seconds and use 228 | 229 | pm2 list 230 | 231 | for a fresh status update. For more information see 232 | 233 | pm2 help 234 | 235 | ### Configure the website in nginx 236 | 237 | We’re using a single configuration, which can be found at: 238 | 239 | sudo vi /etc/nginx/sites-available/default 240 | 241 | nginx is running as a reverse proxy to handle all the public stuff on port 80 for us. It then passes all the traffic we want to to our node application. 242 | 243 | server { 244 | listen 80; 245 | 246 | server_name your-domain.com; 247 | 248 | location / { 249 | proxy_pass http://localhost:{YOUR_PORT}; 250 | proxy_http_version 1.1; 251 | proxy_set_header Upgrade $http_upgrade; 252 | proxy_set_header Connection 'upgrade'; 253 | proxy_set_header Host $host; 254 | proxy_cache_bypass $http_upgrade; 255 | } 256 | } 257 | 258 | After saving the configuration, use 259 | 260 | sudo service nginx reload 261 | 262 | to tell the server it should use it. 263 | 264 | # Backup 265 | 266 | ## Directory structure 267 | 268 | Directory | Path 269 | ------------ | ------------- 270 | Backup Root | /var/backup 271 | Website Backups | /var/backup/www 272 | MongoDB Backups | /var/backup/mongo 273 | nginx Backups | /var/backup/nginx 274 | 275 | cd /var 276 | sudo mkdir backup 277 | sudo chown www backup 278 | cd backup 279 | 280 | ## Backup Scripts 281 | 282 | ### MongoDB 283 | 284 | Save the following shell script as `/var/backup/create-backup-for-mongo` and make it executable: 285 | 286 | #!/bin/bash 287 | 288 | echo "Mongo Backup started" 289 | 290 | BACKUP_TARGET_ROOT="/var/backup/mongo" 291 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 292 | 293 | # Remove all but the latest 7 backups 294 | cd $BACKUP_TARGET_ROOT 295 | rm -rf `ls -t | tail -n +7` 296 | 297 | # Back up all the databases to a new directory 298 | mongodump -o $CURRENT_BACKUP_TARGET --authenticationDatabase admin 299 | 300 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 301 | rm -rf $CURRENT_BACKUP_TARGET 302 | 303 | echo "Mongo Backup finished" 304 | 305 | ### Websites 306 | 307 | Create the script `/var/backup/create-backup-for-www` and make it executable: 308 | 309 | #!/bin/bash 310 | 311 | echo "WWW Backup started" 312 | 313 | BACKUP_SOURCE="/var/www" 314 | 315 | BACKUP_TARGET_ROOT="/var/backup/www" 316 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 317 | 318 | cd $BACKUP_TARGET_ROOT 319 | rm -rf `ls -t | tail -n +7` 320 | 321 | # Back up all the websites to a new directory 322 | rsync -a -E -c --stats $BACKUP_SOURCE $CURRENT_BACKUP_TARGET 323 | 324 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 325 | rm -rf $CURRENT_BACKUP_TARGET 326 | 327 | echo "WWW Backup finished" 328 | 329 | ### nginx 330 | 331 | Create the script `/var/backup/create-backup-for-nginx` and make it executable: 332 | 333 | #!/bin/bash 334 | 335 | echo "nginx Backup started" 336 | 337 | BACKUP_SOURCE="/etc/nginx" 338 | 339 | BACKUP_TARGET_ROOT="/var/backup/nginx" 340 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 341 | 342 | # Remove all but the latest 7 backups 343 | cd $BACKUP_TARGET_ROOT 344 | rm -rf `ls -t | tail -n +7` 345 | 346 | rsync -a -E -c --stats $BACKUP_SOURCE $CURRENT_BACKUP_TARGET 347 | 348 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 349 | rm -rf $CURRENT_BACKUP_TARGET 350 | 351 | echo "nginx Backup finished" 352 | 353 | ## Transfer 354 | 355 | There are many ways to transfer these backup files to another server, I have chosen the way to use rsync over SSH. 356 | 357 | ### Set up SSH 358 | 359 | First create a local key without a password: 360 | 361 | ssh-keygen -f ~/.ssh/id_rsa -q -P "" 362 | 363 | Now get the public key and copy it: 364 | 365 | vi ~/.ssh/id_rsa.pub 366 | 367 | On your backup server add the public SSH key of your web server. If you did not set up SSH before, do it as follows: 368 | 369 | mkdir ~/.ssh 370 | chmod 0700 ~/.ssh 371 | touch ~/.ssh/authorized_keys 372 | chmod 0644 ~/.ssh/authorized_keys 373 | 374 | # Paste the public key here: 375 | vi ~/.ssh/authorized_keys 376 | 377 | 378 | ### Add the Transfer Script 379 | 380 | Create a script that combines all backup actions and that finally transfers everything from the current backup folder to the backup server. Save that script as `/var/backup/create-and-transfer-backups` and make it executable. 381 | 382 | #!/bin/bash 383 | 384 | echo "Global Backup started" 385 | 386 | # Important: use absolute paths to be independent of the user context 387 | /var/backup/create-backup-for-mongo 388 | /var/backup/create-backup-for-www 389 | /var/backup/create-backup-for-nginx 390 | 391 | rsync -avz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress /var/backup www-backup@{ip-of-your-backup-server}:/var/backup 392 | 393 | echo "Global Backup finished" 394 | 395 | ### Schedule backup 396 | 397 | sudo vi /etc/crontab 398 | 399 | Set: 400 | 401 | 402 | # m h dom mon dow user command 403 | 10 14 * * * root bash /var/backup/create-and-transfer-backups 404 | 405 | (Runs the backup every day at 2:10 pm.) 406 | 407 | #### Troubleshooting 408 | 409 | If it doesn't work, check your timezone. If it is set wrong, you can change it easily (Ubuntu): 410 | 411 | sudo dpkg-reconfigure tzdata 412 | 413 | Now restart cron to apply the new setting: 414 | 415 | sudo service cron restart 416 | --------------------------------------------------------------------------------