Installing Mono on VPS with nginx

This tutorial is targeted at experienced Windows .NET developers with minimal Linux knowledge. This tutorial allows you to continue to run PHP sites you currently host in addition to .NET sites. Please excuse my lack of expertise on the subjects described below and feel free wiki talk or email me any constructive criticism or corrections (or just edit this document yourself). I'm documenting this all from memory and repetition of original research, so please forgive any omissions.

If you're an experienced Linux guru, please make appropriate edits or email me to discuss any issues. I would also be very interested in any tips regarding site security and hardening.

If you're experienced and just looking for a faster walkthrough and a script for Apache, the one in this blog looks to be pretty solid. An updated script for 2.6.7 is here

Also, I realize that this is in the commentary style of a blog post more than a wiki convention of factual assertions. Due to my lack of a blog and the accessibility of this wiki, it lives here (for now, at least).

Motivation
I'm a professional .NET developer who continues to spend his career in the Windows line of OSes. I've been using Dreamhost for 7 years primarily for a email and a few one-click installs. I have great respect for those who know PHP, but I don't have the time required to pick it up myself. I am also a big fan of Visual Studio 2010 with the ReSharper plugin. My wife recently began her own business that requires an online presence. While I've been wanting to use DreamHost for .NET hosting ever since I got it, this provided enough impetus to tackle the somewhat daunting task of hosting with Mono.

My wife's business includes the uploading of several photos. I use Gallery2, and it nearly always crashed my VPS during batch uploads. This lead me to do some research on site stability and scalability which lead me to converting from Apache to Nginx (pronounced "engine x"). I later discovered that the memory usage is due to the amount of memory necessary to load a JPEG into memory to allow for resizing. I'm still glad that I converted nonetheless.

Several walkthroughs I've seen include running custom scripts. While several of these scripts look very robust, my limited understanding of Linux makes me hesitent to execute someone else's script on my host. Also, some of DreamHost's internal customizations could conflict with script expectations.

Getting Started

 * Upgrade to VPS hosting or better - In DreamHost (and every shared hosting provider I'm aware of) a Virtual Private Server setup is the cheapest option for obtaining root access
 * Familiarity with a terminal, preferably SSH, to perform the actions below - I used [http://wiki.dreamhost.com/Putty PuTTY's SSH terminal}
 * Familiarity with a terminal text editor is very helpful too, but can be worked around through FTP and chmod (be sure to use UNIX line endings) - I used vi (installed by default). All you really need to know is that you can move around with your arrow keys, pgup and pgdn.  Hit 'i' or 'a' to edit text where the cursor is.  Then hit esc (to exit edit mode), colon, then 'wq' to save and quit (:wq).  I used this tutorial.

Install JJs VPS Memory Manager
This is an optional step, but highly recommended. It automatically adjusts your VPS memory usage right before you need it. See the site here. Follow the installation instructions. It won't completely prevent all out of memory errors resulting from large spikes in server side processing (such as image resizing - which grabs a lot of memory in an instant), but it has added a tremendous amount of stability to my VPS at a very minimal cost.

Install Nginx
First, read through the following wiki page on nginx. Basically, you just set an option in your Panel. Of special note is that .htaccess files are not read by nginx. Access control is instead handled by .conf files. There are many good articles on this conversion, but I won't be going into any detail here. Be sure to familiarize yourself with the information under the "Configuration File Locations" heading.

As a side note, if you plan to continue hosting PHP for one-click installs or any custom work, you can execute the following script to upgrade your nginx PHP to 5.3: http://files.gimmesoda.com/php53-fpm.sh. I believe some other tweaks were necessary to pick up 5.3, but I don't have any record of those changes. If someone knows what they are, please edit this page or let me know via wiki talk or email. The wiki page on Php.ini may help.

Add an Admin User
As the above "Install Nginx" section mentions, you need to add an admin user to modify your config files as well as executing several of the commands later in this tutorial. Go to the Manage Admin Users page and create a user. When logged in with your admin account, you need to preceed commands with 'sudo' to assert your root-level credentials. Any command preceeded by 'sudo' has the potential to bring down all of the sites you are hosting, so always be careful of typos and confident in what you're executing before performing any of these commands.

Create a Cron Job to Support Nginx Recovery from Server Reboot
A 'gotcha' that isn't mentioned in the nginx article is the fact that nginx doesn't restart itself when your OS instance is restarted. This is most commonly associated with using more memory than you have allocated for your VPS. System-wide outages can also create this situation. I called this script "restartPhpNginx.scr".

PGREP="/usr/bin/pgrep" PHPD="php53.cgi" # or "php5.cgi" if you didn't upgrade to 5.3 $PGREP ${PHPD} if [ $? -ne 0 ]; then # No php processes, restart nginx /etc/init.d/nginx stop /etc/init.d/nginx start fi
 * 1) !/bin/bash

(thanks to smc1979 from the thread http://discussion.dreamhost.com/thread-128870.html for creating this script)

If you're using PuTTY and vi, you can copy the above text and then login as your admin user and perform the following:

cd $home vi restartPhpNginx.scr

Then simply hit "i", right-click in the PuTTY window to paste, then hit your "esc" key and type in ":wq" to save the new file and quit vi.

Since your admin user is inaccessible from the "Add New Cron Job" function of your Cron Jobs Panel page, we need to make our own Crontab. From your PuTTY shell, type the following:

sudo crontab -e

This opens your crontab in the "nano" editor. The following line executes the above script once every minute with locking. If you would like to change the interval, please see the Crontab wiki page. Copy the following line (or one with your modified interval) and the right-click in PuTTY to paste:

* * * * * /usr/local/bin/setlock -n /tmp/cronlock.1234567.123456 sh -c $'/bin/bash /home/adminusername/restartPhpNginx.scr'

Then replace adminusername and hit "ctrl-k" and then "x" to save and exit. To confirm your changes, type: sudo crontab -l

At this point, I suggest rebooting your VPS, waiting a few minutes, and then verifying that one of your existing sites is still active. If you have difficulties and need to restore your site while troubleshooting type the following into your PuTTY terminal:

sudo /etc/init.d/nginx start

Prevent DreamHost from Resetting Customizations
In the Panel, go to the VPS Configure Server page. Before setting up Mono, I upgraded my Nginx PHP version to 5.3 (which isn't supported through the panel). So I'm not entirely certain as to what each of these items are necessary for this setup. If someone knows, please help me fill in my knowledge gap here. Note that these options are not available until after you have created your admin user.
 * Under "Web Server Configuration", uncheck "DreamHost Managed"
 * Under "PHP Configuration", uncheck "DreamHost Managed" (this may not be necessary if you're okay with PHP 5.2 for your non-mono sites).

After doing this, you will need to manually edit your nginx.conf file to add new domains. When you add a new domain, DreamHost is nice enough to add a sample config file in the same location as your nginx.conf file so that you can copy out the section created for your new "fully hosted" domain and paste it into your customized nginx.conf file. For more information on editing this file, see the section "Modify Nginx.conf". I'm not sure if there are other impacts to removing those two items from being managed by DreamHost. If there are, I haven't encountered them yet.

Update Apt-get Sources to Include Official Squeeze Packages
This may be entirely unnecessary as I used this while going down the path to install mono from apt-get. Before I executed the command though, I found a fairly simple way to get the latest stable version of mono instead of being stuck with an older version (2.6.7 at the time of this writing). If the proceeding installation of git isn't able to be found, then this step is necessary. If someone follows this tutorial, please email or post to my wiki talk page to let me know.

The Debian documention on apt-get sources.list file is here.

I suggest making a backup of the file first:

cd /etc/apt/ sudo cp sources.list sources.list.monobak vi sources.list

Here are the lines I added to my sources.list file - based on this thread:

deb http://ftp.debian.org/debian/ squeeze main contrib non-free deb-src http://ftp.debian.org/debian/ squeeze main contrib non-free deb ftp://debian.oregonstate.edu/debian/ squeeze main contrib non-free deb-src ftp://debian.oregonstate.edu/debian/ squeeze main contrib non-free deb ftp://debian.oregonstate.edu/debian/ squeeze-proposed-updates main contrib non-free deb-src ftp://debian.oregonstate.edu/debian/ squeeze-proposed-updates main contrib non-free deb ftp://ftp.us.debian.org/debian/ squeeze main contrib non-free deb-src ftp://ftp.us.debian.org/debian/ squeeze main contrib non-free
 * 1) Debian.org:
 * 1) Debian Official Repository Mirror squeeze:
 * 1) Debian US mirror:

Someone please confirm or deny the existence of the git package within the default sources.

Temporarily Increase your VPS Resources
Before running an installation, I like to increase my VPS resources to prevent it from rebooting itself. To do this, either edit the config.php file of JJ's Memory Manager or disable it and increase the amount under Manage Resources on your DreamHost Panel. I usually set it to max memory to be safe. You can verify that the increase has taken effect by refreshing the Manage Resources page.

Install Git
sudo apt-get update sudo apt-get install git-core

It shouldn't take too long to execute.

Compile Newest Stable Mono Source
It may take close to an hour to exeucte the following commands. I walked away while they executed and didn't watch the clock. I selected version 2.10.8 since it is said to be the Latest Stable Version on Mono's official downloads page. The page specific to Debian is less clear about what is Latest Stable, and I didn't want to use 2.6.7.

Install the mono dependancies (this also may require the extra sources added to sources.list):

apt-get build-dep mono-fastcgi-server2

Once complete, we now get the mono source:

cd /usr/src sudo git clone https://github.com/mono/mono.git cd mono sudo git checkout 2.10.8 sudo apt-get install autoconf libtool automake bison sudo ./autogen.sh sudo make sudo make install

The above commands are from this blog. I'm not sure if the version number is necessary. Normally, not specifying a version would produce the latest version. But I don't know enough about git to be able to say for certain, so I left them in. I'm also not sure if all the commands are necessary in the bison line. What I do know is that this worked perfectly for me.

As new bulids become available, I imagine that the above lines can be executed again with the newest build number.

You can probably delete the /usr/src/mono directory once the installation is complete.

Please note that this process does not include libgdiplus. Other tutorials cover this if you're interested, but System.Drawing.dll is not normally referenced from within a web project.

Compile Newest Stable XSP Source
This is pretty much the same as above, except the latest version of xsp is 2.10.2 (I just worked my way down until I stopped getting a "not found" message)

cd /usr/src sudo git clone https://github.com/mono/xsp.git cd xsp sudo git checkout 2.10.2 sudo ./autogen.sh sudo make sudo make install

To my best recollection, I did not execute any apt-get command for xsp.

Return your VPS Resources to their Original State
Now that we're done with the intense server usage, undo what you did in the 'Temporarily Increase your VPS Resources' section.

Modify nginx.conf
The official mono page for the configuration is here. As mentioned in the Nginx wiki page, the file is located at:

/dh/nginx/servers/httpd-psXXXXXX/nginx.conf

Create a backup as we did with sources.list and then open the original with sudo vi. I took the following snippit:

server { listen x.x.x.x:80; server_name yourdomain.com www.yourdomain.com; access_log /home/yourhostinglogin/logs/yourdomain.com/http.xxxxxxxx/access.log combined; error_log /home/yourhostinglogin/logs/yourdomain.com/http.xxxxxxxx/error.log error; root /home/yourhostinglogin/yourdomain.com; index index.html index.htm index.php index.php5; include /home/yourhostinglogin/nginx/yourdomain.com/*;

And made it:

server { listen x.x.x.x:80; server_name yourdomain.com www.yourdomain.com; access_log /home/yourhostinglogin/logs/yourdomain.com/http.xxxxxxxx/access.log combined; error_log /home/yourhostinglogin/logs/yourdomain.com/http.xxxxxxxx/error.log error; root /home/yourhostinglogin/yourdomain.com; index index.html index.htm default.aspx Default.aspx; # This is the starting location of the MVC 3 template. # You can change this to Default.aspx or whatever contains your starting page: fastcgi_index Home/Index; include /dh/nginx/etc/fastcgi_params; include /home/yourhostinglogin/nginx/yourdomain.com/*;

As you can see, one line is modified and two are added. The rest is included for context.

Then at the end of the server section I added:

# Mono location / { fastcgi_pass 127.0.0.1:9000; }

Replace yourdomain.com and yourhostinglogin. Note that this is NOT your admin login. Once complete, save and exit vi.

Modify fastcgi_params
Use sudo vi to make the required additions to your fastcgi_params file in your /dh/nginx/etc/ directory:

fastcgi_param PATH_INFO        ""; fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
 * 1) mono

Build and Publish Your Web Application
I'm sure there are several ways to accomplish this. What I did was create a new MVC 3 web application with Razor. Then I right-clicked on the main project and selected Publish. I selected the "Publish method" of File System and set the Target Location to my local FTP upload directory.

Also note that you will need to set the following assemblies'  property to. Those that don't appear in your current references list need to be added from the .NET references tab.

System.Web.Abstractions System.Web.Helpers System.Web.Mvc System.Web.Razor System.Web.Routing System.Web.WebPages System.Web.WebPages.Deployment System.Web.WebPages.Razor

Upload Your MVC or ASP .NET site
Use your favorite SFTP client to upload your published target directory over to your domain hosting location.

Create and Install XSP Fastcgi Startup Script
This is almost a straight copy/paste from this site. The only change is fastcgi-mono-server2 becomes fastcgi-mono-server4 and the WEBAPPS line. I am including it here in case that page ever becomes unavailable before this one does.

Get to the correct directory and create a script we'll call monoserve: cd /etc/init.d/ sudo vi monoserve

Then copy the following code, hit "i" within vi, then right-click to paste:

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/local/bin/mono NAME=monoserver DESC=monoserver MONOSERVER=$(which fastcgi-mono-server4) MONOSERVER_PID=$(ps auxf | grep fastcgi-mono-server4.exe | grep -v grep | awk '{print $2}') WEBAPPS="www.domain1.xyz:/:/home/yourhostinglogin/domain1.xyz/,www.domain2.xyz:/:/home/yourhostinglogin/domain2.xyz/" case "$1" in        start)                 if [ -z "${MONOSERVER_PID}" ]; then                         echo "starting mono server"                         ${MONOSERVER} /applications=${WEBAPPS} /socket=tcp:127.0.0.1:9000 &                         echo "mono server started"                 else                         echo ${WEBAPPS}                         echo "mono server is running"                 fi         ;;         stop) if [ -n "${MONOSERVER_PID}" ]; then kill ${MONOSERVER_PID} echo "mono server stopped" else echo "mono server is not running" fi        ;; esac exit 0
 * 1) !/bin/sh
 * 1) BEGIN INIT INFO
 * 2) Provides:          monoserve.sh
 * 3) Required-Start:    $local_fs $syslog $remote_fs
 * 4) Required-Stop:     $local_fs $syslog $remote_fs
 * 5) Default-Start:     2 3 4 5
 * 6) Default-Stop:      0 1 6
 * 7) Short-Description: Start fastcgi mono server with hosts
 * 8) END INIT INFO

Change domain1.xyz, domain2.xyz (or remove the comma and everything to the closing quote), and yourhostinglogin. Then save and close vi.

We then need to add the appropriate rights:

sudo chmod +x /etc/init.d/monoserve And install the script: update-rc.d monoserve defaults

Conclusion
At this point, you should be able to reboot your VPS server. Be careful not to click on the new button that says "Reset state of psXXXXX now!", as that may be a new option if you recently created a VPS admin account. Give it a few minutes and then browse to your domain. You should see your .NET site up and running!

If you have any further difficulties specific to .NET, some great migration, setup and howto guides are found at the mono website

Originally created by Akroplax 22:20, 9 April 2012 (PDT)

Troubleshooting
Recently, I received the following error message on all of my mono pages:

Server Error in '/' Application Argument cannot be null. Parameter name: path Description: HTTP 400. Error processing request. Stack Trace: System.ArgumentNullException: Argument cannot be null. Parameter name: path at System.IO.FileSystemInfo.CheckPath (System.String path) [0x00000] in :0 at System.IO.DirectoryInfo..ctor (System.String path, Boolean simpleOriginalPath) [0x00000] in :0 at System.IO.DirectoryInfo..ctor (System.String path) [0x00000] in :0 at (wrapper remoting-invoke-with-check) System.IO.DirectoryInfo:.ctor (string) at Mono.WebServer.FastCgi.WorkerRequest.GetFilePath [0x00000] in :0 at Mono.WebServer.MonoWorkerRequest.GetFilePathTranslated [0x00000] in :0 at Mono.WebServer.MonoWorkerRequest.AssertFileAccessible [0x00000] in :0 at Mono.WebServer.MonoWorkerRequest.ProcessRequest [0x00000] in :0 Version information: Mono Runtime Version: 2.10.8 ((no/ee240bb Sat Apr 7 23:05:58 PDT 2012); ASP.NET Version: 4.0.30319.1

After doing some digging, I checked my fastcgi_params file and found that the fastcgi_param values that I specified earlier in the document had been removed from the file. This was likely due to an unannounced DreamHost update. After restoring the lines to the end of the file, the sites worked again.