├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 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 | # Node.js Web Server Guide 2 | 3 | ## Purpose 4 | 5 | The main purpose of this guide is to summarize all the steps one has to take to get Node.js and MongoDB up and running on a Ubuntu web server. It is not 100% complete but it covers hopefully the most things you need to know to run different Node.js applications behind nginx, using MongoDB as a database. 6 | 7 | If you find anything you can't agree with, please let me know. Send me an email or just a pull request and I will see if I am able to fix this ;-). 8 | 9 | This guide contains some details regarding security that won't necessarily matter if you only want to run a single website on your server. In that case take a look at ["Setting up Ubuntu Server for running a single Website with Node.js and MongoDB"](https://github.com/aspnetde/ubuntu-nodejs-mongodb-guide). 10 | 11 | ## Initial server installation 12 | 13 | ### Make Tools 14 | 15 | The make tools are essential to build some npm packages and other stuff. So it’s generally a good idea to install them early. 16 | 17 | sudo apt-get install gcc make build-essential 18 | 19 | (If the installation of build-essential fails, see Misc/Missing packages section). 20 | 21 | ### nginx 22 | 23 | sudo apt-get install nginx 24 | 25 | 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. 26 | 27 | Also make sure the server starts automatically after booting the system (Should be enabled by default): 28 | 29 | sudo update-rc.d nginx defaults 30 | 31 | ### Node.js 32 | 33 | wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.14.0/install.sh | bash 34 | 35 | # Refresh Path 36 | source ~/.profile 37 | 38 | # Use latest Node.JS version 39 | nvm install v0.11.13 40 | 41 | # Make it default 42 | nvm use default v0.11.13 43 | 44 | ### Bower 45 | 46 | sudo npm install bower -g 47 | 48 | ### PM2 49 | 50 | PM2 manages the different node applications running on this server. 51 | 52 | sudo npm install pm2 -g 53 | 54 | 55 | ### Glances 56 | 57 | Glances can be used to monitor the overall state of the server. 58 | 59 | sudo apt-get install python-pip build-essential python-dev 60 | sudo pip install Glances 61 | sudo apt-get install lm-sensors 62 | sudo pip install PySensors 63 | 64 | ### Git 65 | 66 | sudo apt-get install git 67 | 68 | ### Zip 69 | 70 | sudo apt-get install zip 71 | 72 | ### MongoDB 73 | 74 | 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/). 75 | 76 | #### Install the database service 77 | 78 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 79 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list 80 | sudo apt-get update 81 | sudo apt-get install -y mongodb-org 82 | 83 | #### Pin the current version 84 | 85 | echo "mongodb-org hold" | sudo dpkg --set-selections 86 | echo "mongodb-org-server hold" | sudo dpkg --set-selections 87 | echo "mongodb-org-shell hold" | sudo dpkg --set-selections 88 | echo "mongodb-org-mongos hold" | sudo dpkg --set-selections 89 | echo "mongodb-org-tools hold" | sudo dpkg --set-selections 90 | 91 | #### Set up security 92 | 93 | 94 | ##### Set up mongod.conf 95 | 96 | 97 | sudo vi /etc/mongod.conf 98 | 99 | Set: 100 | 101 | auth = true 102 | bind_ip = 127.0.0.1 103 | 104 | ##### Add admin user 105 | 106 | Open the Mongo Console with `mongo`. 107 | 108 | use admin 109 | db.createUser({ user: "", 110 | pwd: "", 111 | roles: [ "userAdminAnyDatabase", 112 | "dbAdminAnyDatabase", 113 | "readWriteAnyDatabase" 114 | 115 | ]}) 116 | 117 | ##### Restart the service 118 | 119 | sudo service mongod restart 120 | 121 | ## Installation of a new website 122 | 123 | ### Create a MongoDB database 124 | 125 | mongo -u admin -p {adminpassword} --authenticationDatabase admin 126 | 127 | Add a MongoDB user for the website 128 | 129 | use website-com 130 | db.createUser( 131 | { 132 | user: "website-com", 133 | pwd: "{newpassword}", 134 | roles: [ 135 | { role: "readWrite", db: "website-com" } 136 | ]}) 137 | 138 | ### Add the website’s directories 139 | 140 | Websites are organized as follows: 141 | 142 | 143 | The main identifier is the domain name of a website, while dots are replaced by dashes. 144 | 145 | Directory | Path 146 | ------------ | ------------- 147 | Single website’s root | /var/www/xyz-com 148 | Single website’s Git repository | /var/www/xyz-com/repo 149 | Single website’s web root | /var/www/xyz-com/www 150 | 151 | Examples: 152 | 153 | - blog.xyz.de => blog-xyz-de 154 | - website.com => website-com 155 | 156 | ### Add a dedicated website user 157 | 158 | Use the domain name for the user’s name: 159 | 160 | sudo adduser website-com 161 | 162 | ### Make the user the owner of the website’s directories 163 | 164 | In `/var/www` execute: 165 | 166 | sudo chown website-com website-com –R 167 | 168 | ### Create a Git repository 169 | 170 | In `/var/www/website-com/repo` run 171 | 172 | git init --bare 173 | 174 | ### Add the Git deployment hook 175 | 176 | This hook is used to deploy changes made to the master repository. It can be customized for each website depending on the specific needs. 177 | 178 | Go to `/var/www/website-com/repo/hooks` and create a new file called “post-receive”: 179 | 180 | vi post-receive 181 | 182 | Add the following commands to it: 183 | 184 | #!/bin/bash 185 | 186 | # Make this executable: chmod +x post-receive 187 | 188 | PREPARATION_DIR="/var/www/website-com/repo/$(uuidgen)" 189 | WEBSITE_ROOT="/var/www/website-com/www" 190 | PM2_APP_NAME="website-com" 191 | 192 | echo "Deployment started" 193 | 194 | read oldrev newrev branch 195 | 196 | if [[ $branch =~ .*/master$ ]]; 197 | then 198 | echo "Master received. Deploying to production..." 199 | 200 | # Creates a temporary working directory 201 | mkdir $PREPARATION_DIR 202 | 203 | # Checks out the master from the repository 204 | GIT_WORK_TREE="$PREPARATION_DIR" git checkout -f 205 | 206 | # Installing all npm and bower modules/packages 207 | cd $PREPARATION_DIR 208 | npm install --allow-root 209 | bower install --allow-root 210 | 211 | # Removes all files in the Website's root 212 | cd $WEBSITE_ROOT 213 | rm -rf * 214 | 215 | # Copies all files over 216 | cd $PREPARATION_DIR 217 | cp -r . $WEBSITE_ROOT 218 | 219 | # Restart the Website via PM2 220 | pm2 restart $PM2_APP_NAME 221 | 222 | # Removes the preparation directory 223 | rm -R $PREPARATION_DIR 224 | else 225 | echo "$branch successfully received. Nothing to do: only the master branch may be deployed on this server." 226 | fi 227 | 228 | echo "Deployment finished" 229 | 230 | Remember the value of **PM2_APP_NAME** and use it as an identifier for your pm2 application later. 231 | 232 | After saving, make the script executable: 233 | 234 | chmod +x post-receive 235 | 236 | ### Push your application to your repository 237 | 238 | 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/website-com/www` (Check with `ls –l`). 239 | 240 | git clone ssh://{username}@{ipaddress}/var/www/website-com/repo website-com 241 | 242 | 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. 243 | 244 | ### Set up PM2 245 | 246 | #### Give the user temporary root privilige 247 | 248 | Call `visudo` and add the following line: 249 | 250 | {username} ALL=(ALL:ALL) ALL 251 | 252 | #### Run the startup script 253 | 254 | To start pm2 with the system: 255 | 256 | pm2 startup ubuntu 257 | 258 | PM2 will tell you, you have to run this command as root, and print the full command to execute, for example: 259 | 260 | sudo env PATH=$PATH:/home/{username}/.nvm/v0.10.32/bin pm2 startup ubuntu -u {username} 261 | 262 | #### Remove root privilege 263 | 264 | Call `visudo` again and remove the line you did just add before. 265 | 266 | #### Start your application 267 | 268 | cd /var/www/website-com/www/ 269 | pm2 start app.js --name "website-com" 270 | 271 | **Note: The PM2 instance is now running in the context if your website's user. This is especially useful to limit file access, but you can't see any other PM2 process or applications as you could see if running all websites under one single user.** 272 | 273 | If everything works PM2 reponds with `Process {nameofstarting.js}` launched. Wait a few seconds and use 274 | 275 | pm2 list 276 | 277 | for a fresh status update. For more information see 278 | 279 | pm2 help 280 | 281 | ### Configure the website in nginx 282 | 283 | We’re using a single configuration, which can be found at: 284 | 285 | sudo vi /etc/nginx/sites-available/default 286 | 287 | 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. 288 | 289 | server { 290 | listen 80; 291 | 292 | server_name your-domain.com; 293 | 294 | location / { 295 | proxy_pass http://localhost:{YOUR_PORT}; 296 | proxy_http_version 1.1; 297 | proxy_set_header Upgrade $http_upgrade; 298 | proxy_set_header Connection 'upgrade'; 299 | proxy_set_header Host $host; 300 | proxy_cache_bypass $http_upgrade; 301 | } 302 | } 303 | 304 | After saving the configuration, use 305 | 306 | sudo service nginx reload 307 | 308 | to tell the server it should use it. 309 | 310 | ## Backup 311 | 312 | As I am currently using a Hetzner server, I am also able to use 100 GB backup space which they offer free of charge. But anyway: If you're not hosting your server at Hetzner this may also apply to your actual hosting environment. 313 | 314 | ### Directory structure 315 | 316 | Directory | Path 317 | ------------ | ------------- 318 | Backup Root | /var/backup 319 | Website Backups | /var/backup/www 320 | MongoDB Backups | /var/backup/mongo 321 | nginx Backups | /var/backup/nginx 322 | 323 | ### MongoDB 324 | 325 | #### Backup user 326 | 327 | There needs to be a user with a backup role to create backups of all databases: 328 | 329 | use admin 330 | db.createUser( 331 | { 332 | user: "backup”, 333 | pwd: "{newpassword}", 334 | roles: [{ role: "backup", db: "admin" }] 335 | } 336 | ) 337 | 338 | #### Backup script 339 | 340 | Save the following shell script as `/var/backup/create-backup-for-mongo` and make it executable: 341 | 342 | #!/bin/bash 343 | 344 | echo "Mongo Backup started" 345 | 346 | ADMIN_USERNAME="backup" 347 | ADMIN_PASSWORD="{password}" 348 | 349 | BACKUP_TARGET_ROOT="/var/backup/mongo" 350 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 351 | 352 | # Remove all but the latest 30 backups 353 | cd $BACKUP_TARGET_ROOT 354 | rm -rf `ls -t | tail -n +30` 355 | 356 | # Back up all the databases to a new directory 357 | mongodump -u $ADMIN_USERNAME -p $ADMIN_PASSWORD -o $CURRENT_BACKUP_TARGET --authenticationDatabase admin 358 | 359 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 360 | rm -rf $CURRENT_BACKUP_TARGET 361 | 362 | echo "Mongo Backup finished" 363 | 364 | #### Websites 365 | 366 | Create the script `/var/backup/create-backup-for-www` and make it executable: 367 | 368 | #!/bin/bash 369 | 370 | echo "WWW Backup started" 371 | 372 | BACKUP_SOURCE="/var/www" 373 | 374 | BACKUP_TARGET_ROOT="/var/backup/www" 375 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 376 | cd $BACKUP_TARGET_ROOT 377 | rm -rf `ls -t | tail -n +30` 378 | 379 | # Back up all the websites to a new directory 380 | rsync -a -E -c --stats $BACKUP_SOURCE $CURRENT_BACKUP_TARGET 381 | 382 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 383 | rm -rf $CURRENT_BACKUP_TARGET 384 | 385 | echo "WWW Backup finished" 386 | 387 | #### nginx 388 | 389 | Create the script `/var/backup/create-backup-for-nginx` and make it executable: 390 | 391 | #!/bin/bash 392 | 393 | echo "nginx Backup started" 394 | 395 | BACKUP_SOURCE="/etc/nginx" 396 | 397 | BACKUP_TARGET_ROOT="/var/backup/nginx" 398 | CURRENT_BACKUP_TARGET="$BACKUP_TARGET_ROOT/$(uuidgen)" 399 | 400 | # Remove all but the latest 30 backups 401 | cd $BACKUP_TARGET_ROOT 402 | rm -rf `ls -t | tail -n +30` 403 | 404 | rsync -a -E -c --stats $BACKUP_SOURCE $CURRENT_BACKUP_TARGET 405 | 406 | zip -r "$(uuidgen).zip" $CURRENT_BACKUP_TARGET 407 | rm -rf $CURRENT_BACKUP_TARGET 408 | 409 | echo "nginx Backup finished" 410 | 411 | ### Mount the backup server 412 | 413 | 414 | #### Install cifs-utils 415 | 416 | sudo apt-get install cifs-utils 417 | 418 | ##### Mounting 419 | 420 | This will mount the backup server as a local drive and it will reconnect on each reboot (it’s a single line added to fstab). 421 | 422 | sudo vi /etc/fstab 423 | 424 | 425 | //u100445.your-backup.de/backup /mnt/backup-server cifs iocharset=utf8,rw,credentials=/var/backup/backup-credentials.txt,uid={localusername},gid={localusergroupid},file_mode=0660,dir_mode=0770 0 0 426 | 427 | (If you are not aware of the group id, just type `id` and it will be displayed for the current user (in case you want to mount the drive in his/her context)). 428 | 429 | #### Credentials 430 | 431 | vi /var/backup/backup-credentials.txt 432 | 433 | Set: 434 | 435 | username={username} 436 | password={password} 437 | 438 | #### Transfer script 439 | 440 | 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. 441 | 442 | #!/bin/bash 443 | 444 | echo "Global Backup started" 445 | 446 | #Important: use absolute paths to be independent of the user context 447 | /var/backup/create-backup-for-mongo 448 | /var/backup/create-backup-for-www 449 | /var/backup/create-backup-for-nginx 450 | 451 | rsync -avzE --delete --stats /var/backup /mnt/backup-server 452 | 453 | echo "Global Backup finished" 454 | 455 | #### Schedule backup 456 | 457 | sudo vi /etc/crontab 458 | 459 | Set: 460 | 461 | 462 | # m h dom mon dow user command 463 | 10 14 * * * root bash /var/backup/create-and-transfer-backups 464 | 465 | (Runs the backup every day at 2:10 pm.) 466 | 467 | 468 | ## Miscellaneous 469 | 470 | 471 | ### Commands 472 | 473 | Action | Command 474 | ------------ | ------------- 475 | Change User name | passwd 476 | Grant root priviliges | visudo, add: {username} ALL=(ALL:ALL) ALL 477 | System UpdateS: Fetching the list of available updates | sudo apt-get update 478 | System UpdateS: Strictly upgrading the current packages | sudo apt-get upgrade 479 | System UpdateS: Installing updates (new ones) | sudo apt-get dist-upgrade 480 | Reboot | sudo reboot 481 | Remove a directory and its sub-directories recursively | rm –R {directoryname} 482 | Remove everything, files and directories | sudo rm -rf * 483 | Show current network settings | ifconfig 484 | Make rudi the owner of a directory | chown -R rudi {directoryname} 485 | Start a system service | sudo service {servicename} start 486 | Restart a system service | sudo service {servicename} restart 487 | Stop a system service | sudo service {servicename} stop 488 | Reload a system service | sudo service {servicename} reload 489 | See who’s the owner of a process | ps aux | grep {processname} 490 | chmod Details | [http://serverfault.com/a/357109](http://serverfault.com/a/357109) & [http://linuxcommand.org/lts0070.php](http://linuxcommand.org/lts0070.php) 491 | Grant a Role in MongoDB afterwards | db.grantRolesToUser("username", [{ role: "readWrite", db: "dbname" }]) 492 | Make a shell script executable |chmod +x {scriptname} 493 | 494 | 495 | ### Add a SSH shortcut (on the development machine) 496 | 497 | Create (or edit) a file named config in `~/.ssh/` (User's root directory) 498 | 499 | Host username 500 | HostName xxx.xxx.xxx.xxx 501 | User username 502 | 503 | Now just type `ssh username` in terminal. 504 | 505 | ### Packages via apt-get not available? 506 | 507 | If some packages are not available via apt-get, take a look at the sources configuration: 508 | 509 | sudo vi /etc/apt/sources.list 510 | 511 | If the extras and the archive repositories are commented out, just remove the comments. 512 | --------------------------------------------------------------------------------