I updated this article on 8th March 2021 to include extra information for Umbraco 8.
Here is how I like to set up my Web.config file for Umbraco. Some of these settings are personal preference, others make the site more secure. I try and do this at the beginning of a project so that I can test if any of the functionality is altered by these changes.
After a certain time of inactivity, you will be automatically logged-out of the Umbraco back-office. By default, this is set to 20 minutes which I find very inconvenient, especially during development. I prefer to set it to a much longer timeframe under <appSettings>. This can be overridden in the release version – see below.
<!-- Umbraco 7 --> <add key="umbracoTimeOutInMinutes" value="200" /> <!-- Umbraco 8 --> <add key="Umbraco.Core.TimeOutInMinutes" value="200" />
By default, Umbraco will warn the user that the version they are using is now out-of-date and that they should upgrade. These warnings start happening after 7 days, and as Umbraco is very actively developed it seems like they are always there. You can switch them off by adding the following to the <appSettings> section of Web.config. Note, that you need to add this setting, it is not there by default.
<!-- Umbraco 7 --> <add key="umbracoVersionCheckPeriod" value="0" /> <!-- Umbraco 8 --> <add key="Umbraco.Core.VersionCheckPeriod" value="0" />
The Models Builder is an extremely useful tool when developing with Umbraco. It creates strongly-typed C# classes that represent the document types used in the back-office. By default, the Models Builder is set to "PureLive" mode, which means that the classes are automatically built and kept up-to-date within memory. That is fine, except that we can't access the models in the controller code, only in Razor views.
I prefer to set the mode to "Dll", which means they can be used in controllers, but that we have to manually build the classes in the back-office when we need them (or when they change). For Umbraco 8.5 onwards the "Dll" setting no longer exists. My current approach is to use "LiveAppData" to generate the C# classes under the App_Data folder, I then create a pre-build event for my controllers project to copy the .cs files into it.
<!-- Umbraco 7 --> <add key="Umbraco.ModelsBuilder.Enable" value="true" /> <add key="Umbraco.ModelsBuilder.ModelsMode" value="Dll" /> <!-- Umbraco 8 --> <add key="Umbraco.ModelsBuilder.Enable" value="true" /> <add key="Umbraco.ModelsBuilder.ModelsMode" value="LiveAppData" />
Pre-build event command line:
IF EXIST "$(SolutionDir)MyProject\App_Data\Models" xcopy "$(SolutionDir)MyProject\App_Data\Models\*.cs" "$(ProjectDir)PublishedContentModels" /y
If anything goes wrong and there is a system error on the site then we don’t want the end-user to see the error message, but we do want to show it while we are developing. So, I set the <customErrors> tag (under <system.web>) to redirect to a given page when there is an error and set the mode to "RemoteOnly" so that this only happens if the user is not viewing the website on the same machine as it is hosted.
<customErrors mode="RemoteOnly" defaultRedirect="/error.html" />
I always redirect to a plain HTML page for custom errors as we are unsure of the reason and an Umbraco-generated page might also fail. For example, the database may be down.
You also need to change another setting in an Umbraco config file for custom errors to work. Update /config/umbracoSettings.config (near the end of the file) and set trySkipIisCustomErrors to true.
<web.routing trySkipIisCustomErrors="true" internalRedirectPreservesTemplate="false" disableAlternativeTemplates="false" disableFindContentByIdPath="false" umbracoApplicationUrl=""> </web.routing>
This setting also affects the 404 redirect page, which can be set to an Umbraco page id at the top of the same file.
On rare occasions you can have an error triggered by IIS, and it doesn’t even reach your site to fall back to the error page you have set up. This can happen if there is a mistake in your web.config file, being aware that this can happen if your hosting provider updates the IIS software and what used to work suddenly reports an "Internal Server Error".
If you are experiencing this you can set the error mode to "Detailed" under system.webServer. A more descriptive error message will then be displayed, and you can fix the problem. This setting should be disabled most of the time as it reports detailed information about your site, but I tend to keep it commented-out in my web.config in case the worst happens.
<httpErrors errorMode="Detailed" />
As a developer I really do not enjoy tweaking settings to make the site more secure. But I’ve worked in very few places with a DevOps team who specialise in these and often find myself running and re-running security reports and changing settings to achieve the best results while still allowing the site to actually return pages!
This blog entry has a great description of going through this process, here I’ll just describe the settings I tend to use.
In order to limit the ability of nasty people trying to raid your site the first place to start is to limit what can be requested from it. Obviously you can be really strict, but that might also stop your site from working, so the settings here are specific to your site and a trade-off is required between what is allowed by your site’s normal function and what you can stop others from doing.
We can restrict what is allowed by a web request to our site in the system.webServer-->security-->requestFiltering section of web.config. First of all we can restrict which "verbs" are allowed. This restricts the type of request you can make, the most familiar of these will be GET (a normal page request) and POST (when you post a form to your server). There are, however, others that may be used or disallowed, for example HEAD or PUT.
The safest option is to disable all verbs (allowUnlisted="false") and then list those that you need to allow for you site to function. As a minimum you should disable OPTIONS, as below.
Next, you should consider restricting the size of the request (and the sizes of certain components of it). This is done using the requestLimits tag and, again, these will be specific to your site. Try and make them the lowest number possible while still allowing your site to function as required.
The maxAllowedContentLength is the maximum allowed total size of a request in bytes. This will include the URL, headers and any information inside the body of a request. The body contains information posted by a form which itself includes the contents of any uploaded files. In the example below it has been restricted to 4MB. Note, the total request size is also restricted in the httpRuntime tag described below.
The maxUrl and maxQueryString attributes can be used to restrict the size of the URL and query string part of the URL respectively. The size of the request headers can also be restricted, below the content-type header is restricted to 100 bytes.
<security> <requestFiltering> <verbs allowUnlisted="true"> <add verb="OPTIONS" allowed="false" /> </verbs> <requestLimits maxAllowedContentLength="4194304" maxUrl="1024" maxQueryString="1024"> <headerLimits> <remove header="Content-type" /> <add header="Content-type" sizeLimit="100" /> </headerLimits> </requestLimits> </requestFiltering> </security>
The information returned in the response contains headers that inform the browser about the site. These settings can be used to increase the security of the site.
By removing "X-Powered-By" we stop the site informing the caller that it is powered by ASP.NET, the less we tell them the less they know how to attack us!
We can stop (or restrict the use of) our site being rendered in <frame> components of other sites by setting an "X-Frame-Options" tag.
Cross-site-scripting is enabled by default in most browsers, as it makes the site more vulnerable to attacks it is best to block it as below ("X-Xss-Protection ").
To stop the browser treating your response as a different content-type you can set "X-Content-Type-Options" to "nosniff".
We can restrict information sent to other sites about the current request to our own site by setting the "Referrer-Policy" as below.
The httpProtocol tag is placed within the system.webServer tag.
<httpProtocol> <customHeaders> <remove name="X-Powered-By" /> <remove name="X-Frame-Options" /> <add name="X-Frame-Options" value="DENY" /> <remove name="X-Xss-Protection" /> <add name="X-Xss-Protection" value="1; mode=block" /> <remove name="X-Content-Type-Options" /> <add name="X-Content-Type-Options" value="nosniff" /> <remove name="Content-Security-Policy" /> <add name="Content-Security-Policy" value="default-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' www.google-analytics.com tagmanager.google.com www.googletagmanager.com ;style-src 'self' 'unsafe-inline' tagmanager.google.com fonts.googleapis.com ;img-src 'self' www.google-analytics.com;font-src 'self' fonts.gstatic.com; frame-src www.google.com www.youtube.com" /> <remove name="Referrer-Policy" /> <add name="Referrer-Policy" value="same-origin" /> </customHeaders> </httpProtocol>
This tag specifies where the browser can load content from. You specify directives for where scripts (script-src), styles (style-src), images (img-src), frames (frame-src) and other content types can be loaded from. The obvious place to allow is ‘self’ which means such files can be loaded from your own server.
The default-src directive contains the fallback position if other directives aren’t specified.
The allow unsafe-inline and unsafe-eval allows JavaScript and CSS styles to be used within the HTML, these should be used with caution, although many sites use these mechanisms, so you may not have much choice.
Under the system.web tag you can also change the httpRuntime attributes to restrict the maxRequestLength and executionTimeout period (in seconds). Again, set these to the lowest possible values to allow your site to function as required.
When implementing sessions .NET uses a standard cookie name to maintain the session. A potential attacker can see that it is a .NET site from this, so it is a good idea to rename this cookie to something else.
Under the system.web tag you can define the cookie name to be used.
<sessionState cookieName="mysessioncookie" />
Having set up all these wonderful restrictions and making your site as secure as possible you may notice that the Umbraco back-office has stopped working!
You’ll notice towards the end of the web.config file there is a "location" tag with a path of "umbraco". Within here it is possible to specify a different configuration when visiting /umbraco. I found the following settings work for the parts of the back office that I use, but you may need to allow more access in the content-security-policy.
If something isn’t working in the back office, check the browser console and it will usually show you which restriction is affecting the functionality. You can then update the umbraco-specific content-security-policy as required.
<location path="umbraco"> <system.webServer> <urlCompression doStaticCompression="false" doDynamicCompression="false" dynamicCompressionBeforeCache="false" /> <httpProtocol> <customHeaders> <remove name="X-Frame-Options" /> <add name="X-Frame-Options" value="SAMEORIGIN" /> <remove name="Content-Security-Policy" /> <add name="Content-Security-Policy" value="default-src 'self' www.gravatar.com player.vimeo.com *.vimeocdn.com packages.umbraco.org our.umbraco.org;script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data: www.gravatar.com umbraco.tv dashboard.umbraco.org;font-src 'self';frame-src 'self' www.google.com www.youtube.com" /> </customHeaders> </httpProtocol> </system.webServer> </location>
Within your appSettings tag you can add as many key/value pairs as required by your code.
You can pick these values up in your code using the ConfigurationManager helper.
ConfigurationManager.AppSettings[key]
Depending on your environment set up, the following can be added to your main web.config or one of its derivatives (Web.Release.config or Web.Live.config). These improve the security of the site, but can’t be run in my development environment, hence I have listed them separately here.
<system.web> <httpCookies requireSSL="true" /> </system.web> <system.webServer> <httpProtocol> <customHeaders> <add name="Strict-Transport-Security" value="max-age=31536000" xdt:Transform="Insert" /> </customHeaders> </httpProtocol> </system.webServer>