photo of ben szymanski

Setting up ASP.NET/Mono on Mac OS Server

A guide on how to install and configure ASP.NET / Mono on macOS Server.

I have recently spent a matter of days trying to get mod_mono to play nice with Apache httpd (hereafter referenced as just httpd) on Mac OS, and then actually serve out an ASP.NET application. It's been no small feat, so I wanted to document some of the process and kind of put together a reference guide that hopefully answers questions of any other poor soul who attempts this madness.

My end-goal is to get ASP.NET web applications to run on my Mac OS X Server (El Cap, with Server.app 5.0). I am assuming you either already have a completed ASP.NET web application, or some small, empty ASP.NET application you can test with.

Install the MDK from Mono

The first thing you need to do is to download Mono from http://mono-project.com. At the time of this writing, there is a preview MDK build for El Cap, so be sure to grab that if you are indeed running El Capitan (10.11). It may also be a good idea to grab a copy of Xamarin Studio (formerly MonoDevelop), and maybe Visual Studio Code as well.

Here comes the hard part: getting mod_mono, the Apache module that lets Apache dish out all of your sweet, sweet .NET code from a UNIX box to the internet.

Install Homebrew

If you do not already have Homebrew installed on your Mac, you should get it installed pronto. Not only will we be using it here, but it's also very handy to have around if you ever need to install a one-off piece of software.

Homebrew Homepage

Install pkgconfig

pkgconfig is a utility we will need later when we compile mod_mono. The easiest way to install it is from Homebrew using the brew command.

Run the following command in Terminal.app:

$ brew install pkgconfig

Install Xcode Command-Line Tools

In particular, we need to install Xcode command-line tools to get libtool installed. This is also going to get used when we compile mod_mono.

Run the following command in Terminal.app:

$ xcode-select --install

Get mod_mono Source

mod_mono is a module that plugs into httpd and kicks off everything needed to serve out .NET to the internet. When httpd launches, mod_mono kicks off a new Mono process. The new Mono process is the runtime upon which your ASP.NET application will run.

You can download the source to mod_mono from the mono project site. You'll want to grab a copy of the latest, which is mod_mono-3.12.tar.gz at the time of this writing.

After the download completes, run the following commands in Terminal.app:

$ mv ~/Downloads/mod_mono-3.12.tar /usr/local
$ cd /usr/local
$ tar -zxvf mod_mono-3.12.tar

Disable El Capitan "rootless"

El Capitan instills a new security measure that prevents anyone or anything from modifying system files. In particular, everything in /usr is protected, even if you try to modify something in /usr using root/sudo. The exception is that you are still able to modify things in /usr/local just fine.

The problem is that when mod_mono installs itself, it wants to put itself into /usr/libexec/apache2, where all of the other httpd modules are located. Because of rootless, /usr/libexec/apache2 is unable to be modified, so the mod_mono install script will fail.

To get around this, we need to disable rootless.

  1. Restart your Mac.
  2. Press and hold cmd-r when you hear the startup chime.
  3. When the system boots, Open Terminal.app by going to Utilities > Terminal, from the menubar.
  4. Type the following commands in:

    $ csrutil disable $ reboot

Build mod_mono

Once your Mac has restarted after disabling rootless, open Terminal.app and type the following:

$ cd /usr/local/mod_mono-3.12
$ ./configure prefix=/Library/Frameworks/Mono.framework/Versions/4.2.1
$ make
$ sudo make install

Once your machine finishes, you should be able to see mod_mono.so located in /usr/libexec/apache2.

Enable El Capitan "rootless"

Now that mod_mono.so has been installed in its rightful location, you can re-enable rootless.

  1. Restart your Mac.
  2. Press and hold cmd-r when you hear the startup chime.
  3. When the system boots, Open Terminal.app by going to Utilities > Terminal, from the menubar.
  4. Type the following commands in:

    $ csrutil enable $ reboot

When your machine restarts this time, rootless protection will be once again enabled, and mod_mono.so will still be installed in /usr/libexec/apache2.

Integrating mod_mono with Apache on OS X Server

First, a little bit of discussion. Every Mac comes bundled with Apache httpd installed. You can easily control it by using these three commands:

$ sudo apachectl start
$ sudo apachectl stop
$ sudo apachectl restart
$ apachectl configtest

Other Points of Interest:

  1. Bundled httpd modules are located in /usr/libexec/apache2
  2. httpd configuration files are located in /private/etc/apache2
  3. The default document directory is located at /Library/WebServer/Documents
  4. Type $ man apachectl into Terminal.app to see everything it can do.

When you install Apple's Server.app, a few things get changed. The biggest thing is that we can't just crack open httpd.conf and add a virtual host section for our Mono application. Instead, Apple has us configuring web applications with plist files, which can then be enabled in Server.app. Sound confusing? Uh-huh. Follow along!

Create a New webapp.plist File

  1. Open a new Finder window and navigate to /Library/Server/Web/Config/apache2/webapps.
  2. You should see a file named com.example.mywebapp.plist. Copy it to your Desktop.
  3. Adjust the keys and values in this file to suit your ASP.NET application. You can edit the file using Xcode's plist editor, or by opening it in any old text-editor.
  4. Rename the file to something more like com.yourname.webappname.plist, then copy back into /Library/Server/Web/Config/apache2/webapps.

When it comes to adjusting the keys and values of the plist file, it can be a bit confusing as to what some of these keys and values mean, and whether you actually need them. A good reference on what all of this means can be found by typing into Terminal.app:

$ man webapp.plist

But, I am going to expedite this for you by providing a plist file I created for myself.

Example Config Plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>includeFiles</key>
    <array>
        <string>/private/etc/apache2/mod_mono.conf</string>
        <string>/Library/Server/Web/Config/apache2/httpd_yourappname.conf</string>
    </array>
    <key>launchKeys</key>
    <array>
        <string>com.yourname.yourappname</string>
    </array>
    <key>name</key>
    <string>com.yourname.yourappname</string>
    <key>displayName</key>
    <string>Your App Name</string>
    <key>proxies</key>
    <dict/>
    <key>requiredModuleNames</key>
    <array>
        <string>mod_mono</string>
    </array>
    <key>requiredWebAppNames</key>
    <array>
        <string>com.yourname.webapp.yourappname</string>
    </array>
    <key>installationIndicatorFilePath</key>
    <string>/usr/local/bin/webappfile</string>
</dict>
</plist>

Points of Interest:

  1. When mod_mono installed itself, it left a handy config file in /private/etc/apache2/mod_mono.conf. This file will automatically load the mod_mono module into httpd on startup. It will register all of the typical ASP.NET filetypes (.aspx, .asmx, .ashx...) with httpd. We are going to put a link to this file in the includeFiles section.
  2. In addition to the base mod_mono config file, mod_mono also needs to know a few things about your ASP.NET application. We'll get to this in a second, but you will have a second configuration file located in /Library/Server/Web/Config/apache2 that contains a few extra directives to make everything work.
  3. Replace all of the yourname and yourappname placeholder values with appropriate values.
  4. In the requiredModuleNames section, include a value for "mod_mono".
  5. In the installationIndicatorFilePath, add an absolute path to wherever one of your ASP.NET DLLs is located. For example: /usr/local/bin/aspnetapp/bin/app.dll.

The Second Config File

As I mentioned above, there's a second configuration file that needs to be included. This file contains all of the extra directives for mod_mono and httpd. For a complete reference, please visit the Mono documentation located here, and here.

This configuration file can be made using any text editor. When you are finished, save it in: /Library/Server/Web/Config/apache2.

But, to expedite this as well I'm going to share a working config file again:

Example Config File

DocumentRoot "/usr/local/bin/aspnetapp"
MonoAutoApplication disabled
AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd
KeepAlive off

MonoServerPath appname "/Library/Frameworks/Mono.framework/Versions/4.2.1/bin/mod-mono-server4"

MonoSetEnv appname MONO_IOMAP=all
MonoSetEnv appname MONO_MANAGED_WATCHER=disabled

AddMonoApplications appname "/:/usr/local/bin/aspnetapp"

<Location "/">
    Allow from all
    Order allow,deny
    Require all granted
    MonoSetServerAlias appname
    SetHandler mono
</Location>

<Location "/mono">
    SetHandler mono-ctrl
    Order deny,allow
    Deny from all
    Allow from 127.0.0.1
</Location>

Points of Interest:

  1. Set the DocumentRoot to the root folder of your ASP.NET application.
  2. MonoAutoApplication should be set to disabled. You can read more about automatic/easy configuration in the first documentation link above.
  3. KeepAlive should be set to off. Helps with performance.
  4. MonoServerPath has two parts to it. The first is going to be the name of the Mono server instance, the second is the full path to mod-mono-server4. mod-mono-server4 is nothing more than an executable script with two lines in it that kicks off an instance of Mono.
  5. MonoSetEnv also has two parts. The first is the name of the Mono server instance, the second is MONO_IOMAP=all which I have no idea what it does but it was in the documentation.
  6. AddMonoApplications has two parts, again. The name of the Mono server instance and the location to the root of your ASP.NET application. Note the "/:" that has been prepended. It's important, but more so if you decide to host multiple applications on one Mono server instance (see documentation).
  7. In this case, I am going to map "/" to our webapp using a location directive.
  8. You may also optionally include a "/mono" location, which gives you a barebones web interface for managing the instance of Mono server. Notice how it's been set to 127.0.0.1... this location should be restricted to either localhost or at the very least, your local network.
  9. After deploying and application and publishing this article, I started to notice that sometimes, certain static assets would not load. By typing the complete URL to one of the static assets that was not loading, I got an error page from mod_mono telling me that there was an error thrown by System.IO.IOException stating "Too many files open." Interesting. After a little searching, I came across a GitHub discussion where one "antonmes" suggested disabling "MONO_MANAGED_WATCHER" in the Mono environment. I have appended my example config above with this line. I believe this is correct, but I suppose time will tell.

The Worst is Over: The Final Step

Did you make it this far?

I know, I know! What a miserable journey just to get your sweet, sweet .NET code to run on Mac OS Server. There is one last thing you must do, and after seeing what it is, after having been through everything you just put yourself through, you will either be delighted or mortified.

  1. Open Server.app and click on Websites in the sidebar.
  2. Click the plus button underneath the list of websites.
  3. Configure the website as needed (host name, port number, ssl...).
  4. Click the Edit Advanced Settings button.
  5. In the sheet that pops down, if you did everything right, you should see My Web App (or whatever you named it in the plist config file) in the list.
  6. Click the checkbox next to it to enable it, click OK, then OK again.
  7. Turn on the Websites service, and you should now be able to access your ASP.NET application, being served from httpd and mod_mono!

Edit Advanced Settings button in Server.app

Screenshot of Server.app advanced settings window.

Reference

I found the following links helpful throughout this process: