diff --git a/frontend/src/Components/Form/EnhancedSelectInput.css b/frontend/src/Components/Form/EnhancedSelectInput.css
index aa997f377..363ca7dd7 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.css
+++ b/frontend/src/Components/Form/EnhancedSelectInput.css
@@ -5,6 +5,10 @@
   align-items: center;
 }
 
+.editableContainer {
+  width: 100%;
+}
+
 .hasError {
   composes: hasError from '~Components/Form/Input.css';
 }
@@ -22,6 +26,16 @@
   margin-left: 12px;
 }
 
+.dropdownArrowContainerEditable {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding-right: 17px;
+  width: 30%;
+  height: 35px;
+  text-align: right;
+}
+
 .dropdownArrowContainerDisabled {
   composes: dropdownArrowContainer;
 
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js
index bc9917caf..197375bb6 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.js
+++ b/frontend/src/Components/Form/EnhancedSelectInput.js
@@ -17,6 +17,7 @@ import getUniqueElememtId from 'Utilities/getUniqueElementId';
 import { isMobile as isMobileUtil } from 'Utilities/mobile';
 import HintedSelectInputOption from './HintedSelectInputOption';
 import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
+import TextInput from './TextInput';
 import styles from './EnhancedSelectInput.css';
 
 function isArrowKey(keyCode) {
@@ -169,11 +170,21 @@ class EnhancedSelectInput extends Component {
     }
   }
 
+  onFocus = () => {
+    if (this.state.isOpen) {
+      this._removeListener();
+      this.setState({ isOpen: false });
+    }
+  }
+
   onBlur = () => {
-    // Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
-    const origIndex = getSelectedIndex(this.props);
-    if (origIndex !== this.state.selectedIndex) {
-      this.setState({ selectedIndex: origIndex });
+    if (!this.props.isEditable) {
+      // Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
+      const origIndex = getSelectedIndex(this.props);
+
+      if (origIndex !== this.state.selectedIndex) {
+        this.setState({ selectedIndex: origIndex });
+      }
     }
   }
 
@@ -297,16 +308,19 @@ class EnhancedSelectInput extends Component {
     const {
       className,
       disabledClassName,
+      name,
       value,
       values,
       isDisabled,
+      isEditable,
       isFetching,
       hasError,
       hasWarning,
       valueOptions,
       selectedValueOptions,
       selectedValueComponent: SelectedValueComponent,
-      optionComponent: OptionComponent
+      optionComponent: OptionComponent,
+      onChange
     } = this.props;
 
     const {
@@ -332,52 +346,94 @@ class EnhancedSelectInput extends Component {
                   whitelist={['width']}
                   onMeasure={this.onMeasure}
                 >
-                  <Link
-                    className={classNames(
-                      className,
-                      hasError && styles.hasError,
-                      hasWarning && styles.hasWarning,
-                      isDisabled && disabledClassName
-                    )}
-                    isDisabled={isDisabled}
-                    onBlur={this.onBlur}
-                    onKeyDown={this.onKeyDown}
-                    onPress={this.onPress}
-                  >
-                    <SelectedValueComponent
-                      value={value}
-                      values={values}
-                      {...selectedValueOptions}
-                      {...selectedOption}
-                      isDisabled={isDisabled}
-                      isMultiSelect={isMultiSelect}
-                    >
-                      {selectedOption ? selectedOption.value : null}
-                    </SelectedValueComponent>
-
-                    <div
-                      className={isDisabled ?
-                        styles.dropdownArrowContainerDisabled :
-                        styles.dropdownArrowContainer
-                      }
-                    >
-
-                      {
-                        isFetching &&
-                          <LoadingIndicator
-                            className={styles.loading}
-                            size={20}
-                          />
-                      }
-
-                      {
-                        !isFetching &&
-                          <Icon
-                            name={icons.CARET_DOWN}
-                          />
-                      }
-                    </div>
-                  </Link>
+                  {
+                    isEditable ?
+                      <div
+                        className={styles.editableContainer}
+                      >
+                        <TextInput
+                          className={className}
+                          name={name}
+                          value={value}
+                          readOnly={isDisabled}
+                          hasError={hasError}
+                          hasWarning={hasWarning}
+                          onFocus={this.onFocus}
+                          onBlur={this.onBlur}
+                          onChange={onChange}
+                        />
+                        <Link
+                          className={classNames(
+                            styles.dropdownArrowContainerEditable,
+                            isDisabled ?
+                              styles.dropdownArrowContainerDisabled :
+                              styles.dropdownArrowContainer)
+                          }
+                          onPress={this.onPress}
+                        >
+                          {
+                            isFetching &&
+                              <LoadingIndicator
+                                className={styles.loading}
+                                size={20}
+                              />
+                          }
+
+                          {
+                            !isFetching &&
+                              <Icon
+                                name={icons.CARET_DOWN}
+                              />
+                          }
+                        </Link>
+                      </div> :
+                      <Link
+                        className={classNames(
+                          className,
+                          hasError && styles.hasError,
+                          hasWarning && styles.hasWarning,
+                          isDisabled && disabledClassName
+                        )}
+                        isDisabled={isDisabled}
+                        onBlur={this.onBlur}
+                        onKeyDown={this.onKeyDown}
+                        onPress={this.onPress}
+                      >
+                        <SelectedValueComponent
+                          value={value}
+                          values={values}
+                          {...selectedValueOptions}
+                          {...selectedOption}
+                          isDisabled={isDisabled}
+                          isMultiSelect={isMultiSelect}
+                        >
+                          {selectedOption ? selectedOption.value : null}
+                        </SelectedValueComponent>
+
+                        <div
+                          className={isDisabled ?
+                            styles.dropdownArrowContainerDisabled :
+                            styles.dropdownArrowContainer
+                          }
+                        >
+
+                          {
+                            isFetching &&
+                              <LoadingIndicator
+                                className={styles.loading}
+                                size={20}
+                              />
+                          }
+
+                          {
+                            !isFetching &&
+                              <Icon
+                                name={icons.CARET_DOWN}
+                              />
+                          }
+                        </div>
+                      </Link>
+                  }
                 </Measure>
               </div>
             )}
@@ -502,6 +558,7 @@ EnhancedSelectInput.propTypes = {
   values: PropTypes.arrayOf(PropTypes.object).isRequired,
   isDisabled: PropTypes.bool.isRequired,
   isFetching: PropTypes.bool.isRequired,
+  isEditable: PropTypes.bool.isRequired,
   hasError: PropTypes.bool,
   hasWarning: PropTypes.bool,
   valueOptions: PropTypes.object.isRequired,
@@ -517,6 +574,7 @@ EnhancedSelectInput.defaultProps = {
   disabledClassName: styles.isDisabled,
   isDisabled: false,
   isFetching: false,
+  isEditable: false,
   valueOptions: {},
   selectedValueOptions: {},
   selectedValueComponent: HintedSelectInputSelectedValue,
diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js
index 69d0511c8..7a453fef0 100644
--- a/frontend/src/Components/Form/FormInputGroup.js
+++ b/frontend/src/Components/Form/FormInputGroup.js
@@ -25,6 +25,7 @@ import SeriesTypeSelectInput from './SeriesTypeSelectInput';
 import TagInputConnector from './TagInputConnector';
 import TextInput from './TextInput';
 import TextTagInputConnector from './TextTagInputConnector';
+import UMaskInput from './UMaskInput';
 import styles from './FormInputGroup.css';
 
 function getComponent(type) {
@@ -92,6 +93,9 @@ function getComponent(type) {
     case inputTypes.TEXT_TAG:
       return TextTagInputConnector;
 
+    case inputTypes.UMASK:
+      return UMaskInput;
+
     default:
       return TextInput;
   }
@@ -199,7 +203,7 @@ function FormInputGroup(props) {
       }
 
       {
-        !checkInput && helpTextWarning &&
+        (!checkInput || helpText) && helpTextWarning &&
           <FormInputHelpText
             text={helpTextWarning}
             isWarning={true}
diff --git a/frontend/src/Components/Form/UMaskInput.css b/frontend/src/Components/Form/UMaskInput.css
new file mode 100644
index 000000000..7b687caf9
--- /dev/null
+++ b/frontend/src/Components/Form/UMaskInput.css
@@ -0,0 +1,53 @@
+.inputWrapper {
+  display: flex;
+}
+
+.inputFolder {
+  composes: input from '~Components/Form/Input.css';
+
+  max-width: 100px;
+}
+
+.inputUnitWrapper {
+  position: relative;
+  width: 100%;
+}
+
+.inputUnit {
+  composes: inputUnit from '~Components/Form/FormInputGroup.css';
+
+  right: 40px;
+  font-family: $monoSpaceFontFamily;
+}
+
+.unit {
+  font-family: $monoSpaceFontFamily;
+}
+
+.details {
+  margin-top: 5px;
+  margin-left: 17px;
+  line-height: 20px;
+
+  > div {
+    display: flex;
+
+    label {
+      flex: 0 0 50px;
+    }
+
+    .value {
+      width: 50px;
+      text-align: right;
+    }
+
+    .unit {
+      width: 90px;
+      text-align: right;
+    }
+  }
+}
+
+.readOnly {
+  background-color: #eee;
+}
diff --git a/frontend/src/Components/Form/UMaskInput.js b/frontend/src/Components/Form/UMaskInput.js
new file mode 100644
index 000000000..22f51c8fc
--- /dev/null
+++ b/frontend/src/Components/Form/UMaskInput.js
@@ -0,0 +1,133 @@
+/* eslint-disable no-bitwise */
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import EnhancedSelectInput from './EnhancedSelectInput';
+import styles from './UMaskInput.css';
+
+const umaskOptions = [
+  {
+    key: '755',
+    value: '755 - Owner write, Everyone else read',
+    hint: 'drwxr-xr-x'
+  },
+  {
+    key: '775',
+    value: '775 - Owner & Group write, Other read',
+    hint: 'drwxrwxr-x'
+  },
+  {
+    key: '770',
+    value: '770 - Owner & Group write',
+    hint: 'drwxrwx---'
+  },
+  {
+    key: '750',
+    value: '750 - Owner write, Group read',
+    hint: 'drwxr-x---'
+  },
+  {
+    key: '777',
+    value: '777 - Everyone write',
+    hint: 'drwxrwxrwx'
+  }
+];
+
+function formatPermissions(permissions) {
+
+  const hasSticky = permissions & 0o1000;
+  const hasSetGID = permissions & 0o2000;
+  const hasSetUID = permissions & 0o4000;
+
+  let result = '';
+
+  for (let i = 0; i < 9; i++) {
+    const bit = (permissions & (1 << i)) !== 0;
+    let digit = bit ? 'xwr'[i % 3] : '-';
+    if (i === 6 && hasSetUID) {
+      digit = bit ? 's' : 'S';
+    } else if (i === 3 && hasSetGID) {
+      digit = bit ? 's' : 'S';
+    } else if (i === 0 && hasSticky) {
+      digit = bit ? 't' : 'T';
+    }
+    result = digit + result;
+  }
+
+  return result;
+}
+
+class UMaskInput extends Component {
+
+  //
+  // Render
+
+  render() {
+    const {
+      name,
+      value,
+      onChange
+    } = this.props;
+
+    const valueNum = parseInt(value, 8);
+    const umaskNum = 0o777 & ~valueNum;
+    const umask = umaskNum.toString(8).padStart(4, '0');
+    const folderNum = 0o777 & ~umaskNum;
+    const folder = folderNum.toString(8).padStart(3, '0');
+    const fileNum = 0o666 & ~umaskNum;
+    const file = fileNum.toString(8).padStart(3, '0');
+
+    const unit = formatPermissions(folderNum);
+
+    const values = umaskOptions.map((v) => {
+      return { ...v, hint: <span className={styles.unit}>{v.hint}</span> };
+    });
+
+    return (
+      <div>
+        <div className={styles.inputWrapper}>
+          <div className={styles.inputUnitWrapper}>
+            <EnhancedSelectInput
+              name={name}
+              value={value}
+              values={values}
+              isEditable={true}
+              onChange={onChange}
+            />
+
+            <div className={styles.inputUnit}>
+              d{unit}
+            </div>
+          </div>
+        </div>
+        <div className={styles.details}>
+          <div>
+            <label>UMask</label>
+            <div className={styles.value}>{umask}</div>
+          </div>
+          <div>
+            <label>Folder</label>
+            <div className={styles.value}>{folder}</div>
+            <div className={styles.unit}>d{formatPermissions(folderNum)}</div>
+          </div>
+          <div>
+            <label>File</label>
+            <div className={styles.value}>{file}</div>
+            <div className={styles.unit}>{formatPermissions(fileNum)}</div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+UMaskInput.propTypes = {
+  name: PropTypes.string.isRequired,
+  value: PropTypes.string.isRequired,
+  hasError: PropTypes.bool,
+  hasWarning: PropTypes.bool,
+  onChange: PropTypes.func.isRequired,
+  onFocus: PropTypes.func,
+  onBlur: PropTypes.func
+};
+
+export default UMaskInput;
diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js
index 8daeb9eb0..f71f4f531 100644
--- a/frontend/src/Helpers/Props/inputTypes.js
+++ b/frontend/src/Helpers/Props/inputTypes.js
@@ -20,6 +20,7 @@ export const DYNAMIC_SELECT = 'dynamicSelect';
 export const TAG = 'tag';
 export const TEXT = 'text';
 export const TEXT_TAG = 'textTag';
+export const UMASK = 'umask';
 
 export const all = [
   AUTO_COMPLETE,
@@ -43,5 +44,6 @@ export const all = [
   SERIES_TYPE_SELECT,
   TAG,
   TEXT,
-  TEXT_TAG
+  TEXT_TAG,
+  UMASK
 ];
diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js
index 7a50f4741..e224b6af0 100644
--- a/frontend/src/Settings/MediaManagement/MediaManagement.js
+++ b/frontend/src/Settings/MediaManagement/MediaManagement.js
@@ -383,17 +383,32 @@ class MediaManagement extends Component {
                         advancedSettings={advancedSettings}
                         isAdvanced={true}
                       >
-                        <FormLabel>File chmod mode</FormLabel>
+                        <FormLabel>chmod Folder</FormLabel>
+
+                        <FormInputGroup
+                          type={inputTypes.UMASK}
+                          name="chmodFolder"
+                          helpText="Octal, applied during import/rename to media folders and files (without execute bits)"
+                          helpTextWarning="This only works if the user running Lidarr is the owner of the file. It's better to ensure the download client sets the permissions properly."
+                          onChange={onInputChange}
+                          {...settings.chmodFolder}
+                        />
+                      </FormGroup>
+
+                      <FormGroup
+                        advancedSettings={advancedSettings}
+                        isAdvanced={true}
+                      >
+                        <FormLabel>chown Group</FormLabel>
 
                         <FormInputGroup
                           type={inputTypes.TEXT}
-                          name="fileChmod"
-                          helpTexts={[
-                            'Octal, applied to media files when imported/renamed by Readarr',
-                            'The same mode is applied to movie/sub folders with the execute bit added, e.g., 0644 becomes 0755'
-                          ]}
+                          name="chownGroup"
+                          helpText="Group name or gid. Use gid for remote file systems."
+                          helpTextWarning="This only works if the user running Readarr is the owner of the file. It's better to ensure the download client uses the same group as Readarr."
+                          values={fileDateOptions}
                           onChange={onInputChange}
-                          {...settings.fileChmod}
+                          {...settings.chownGroup}
                         />
                       </FormGroup>
                     </FieldSet>
diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
index cc1e407ac..59a844526 100644
--- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs
+++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs
@@ -37,7 +37,7 @@ namespace NzbDrone.Common.Disk
         public abstract long? GetAvailableSpace(string path);
         public abstract void InheritFolderPermissions(string filename);
         public abstract void SetEveryonePermissions(string filename);
-        public abstract void SetPermissions(string path, string mask);
+        public abstract void SetPermissions(string path, string mask, string group);
         public abstract void CopyPermissions(string sourcePath, string targetPath);
         public abstract long? GetTotalSize(string path);
 
@@ -534,7 +534,7 @@ namespace NzbDrone.Common.Disk
             }
         }
 
-        public virtual bool IsValidFilePermissionMask(string mask)
+        public virtual bool IsValidFolderPermissionMask(string mask)
         {
             throw new NotSupportedException();
         }
diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs
index 1c94daad3..6eb8a9aa9 100644
--- a/src/NzbDrone.Common/Disk/IDiskProvider.cs
+++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs
@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Disk
         long? GetAvailableSpace(string path);
         void InheritFolderPermissions(string filename);
         void SetEveryonePermissions(string filename);
-        void SetPermissions(string path, string mask);
+        void SetPermissions(string path, string mask, string group);
         void CopyPermissions(string sourcePath, string targetPath);
         long? GetTotalSize(string path);
         DateTime FolderGetCreationTime(string path);
@@ -58,6 +58,6 @@ namespace NzbDrone.Common.Disk
         List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly);
         void RemoveEmptySubfolders(string path);
         void SaveStream(Stream stream, string path);
-        bool IsValidFilePermissionMask(string mask);
+        bool IsValidFolderPermissionMask(string mask);
     }
 }
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index d16040161..86408e2d7 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -255,11 +255,18 @@ namespace NzbDrone.Core.Configuration
             set { SetValue("SetPermissionsLinux", value); }
         }
 
-        public string FileChmod
+        public string ChmodFolder
         {
-            get { return GetValue("FileChmod", "0644"); }
+            get { return GetValue("ChmodFolder", "755"); }
 
-            set { SetValue("FileChmod", value); }
+            set { SetValue("ChmodFolder", value); }
+        }
+
+        public string ChownGroup
+        {
+            get { return GetValue("ChownGroup", ""); }
+
+            set { SetValue("ChownGroup", value); }
         }
 
         public string MetadataSource
diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs
index 4d0debfe4..b55783f7c 100644
--- a/src/NzbDrone.Core/Configuration/IConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/IConfigService.cs
@@ -42,7 +42,8 @@ namespace NzbDrone.Core.Configuration
 
         //Permissions (Media Management)
         bool SetPermissionsLinux { get; set; }
-        string FileChmod { get; set; }
+        string ChmodFolder { get; set; }
+        string ChownGroup { get; set; }
 
         //Indexers
         int Retention { get; set; }
diff --git a/src/NzbDrone.Core/Datastore/Migration/006_remove_chown_and_folderchmod_config.cs b/src/NzbDrone.Core/Datastore/Migration/006_remove_chown_and_folderchmod_config.cs
index 3db6c0f7a..a1ead826d 100644
--- a/src/NzbDrone.Core/Datastore/Migration/006_remove_chown_and_folderchmod_config.cs
+++ b/src/NzbDrone.Core/Datastore/Migration/006_remove_chown_and_folderchmod_config.cs
@@ -1,4 +1,7 @@
+using System;
+using System.Data;
 using FluentMigrator;
+using NzbDrone.Common.Extensions;
 using NzbDrone.Core.Datastore.Migration.Framework;
 
 namespace NzbDrone.Core.Datastore.Migration
@@ -9,6 +12,45 @@ namespace NzbDrone.Core.Datastore.Migration
         protected override void MainDbUpgrade()
         {
             Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')");
+            Execute.WithConnection(ConvertFileChmodToFolderChmod);
+        }
+
+        private void ConvertFileChmodToFolderChmod(IDbConnection conn, IDbTransaction tran)
+        {
+            using (IDbCommand getFileChmodCmd = conn.CreateCommand())
+            {
+                getFileChmodCmd.Transaction = tran;
+                getFileChmodCmd.CommandText = @"SELECT Value FROM Config WHERE Key = 'filechmod'";
+
+                var fileChmod = getFileChmodCmd.ExecuteScalar() as string;
+                if (fileChmod != null)
+                {
+                    if (fileChmod.IsNotNullOrWhiteSpace())
+                    {
+                        // Convert without using mono libraries. We take the 'r' bits and shifting them to the 'x' position, preserving everything else.
+                        var fileChmodNum = Convert.ToInt32(fileChmod, 8);
+                        var folderChmodNum = fileChmodNum | ((fileChmodNum & 0x124) >> 2);
+                        var folderChmod = Convert.ToString(folderChmodNum, 8).PadLeft(3, '0');
+
+                        using (IDbCommand insertCmd = conn.CreateCommand())
+                        {
+                            insertCmd.Transaction = tran;
+                            insertCmd.CommandText = "INSERT INTO Config (Key, Value) VALUES ('chmodfolder', ?)";
+                            insertCmd.AddParameter(folderChmod);
+
+                            insertCmd.ExecuteNonQuery();
+                        }
+                    }
+
+                    using (IDbCommand deleteCmd = conn.CreateCommand())
+                    {
+                        deleteCmd.Transaction = tran;
+                        deleteCmd.CommandText = "DELETE FROM Config WHERE Key = 'filechmod'";
+
+                        deleteCmd.ExecuteNonQuery();
+                    }
+                }
+            }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs
index cb9097453..9f1caf6c1 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileAttributeService.cs
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.MediaFiles
             }
             else
             {
-                SetMonoPermissions(path, _configService.FileChmod);
+                SetMonoPermissions(path);
             }
         }
 
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.MediaFiles
         {
             if (OsInfo.IsNotWindows)
             {
-                SetMonoPermissions(path, _configService.FileChmod);
+                SetMonoPermissions(path);
             }
         }
 
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.MediaFiles
             }
         }
 
-        private void SetMonoPermissions(string path, string permissions)
+        private void SetMonoPermissions(string path)
         {
             if (!_configService.SetPermissionsLinux)
             {
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles
 
             try
             {
-                _diskProvider.SetPermissions(path, permissions);
+                _diskProvider.SetPermissions(path, _configService.ChmodFolder, _configService.ChownGroup);
             }
             catch (Exception ex)
             {
diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs
index 419c2102a..af405dd3f 100644
--- a/src/NzbDrone.Core/Update/InstallUpdateService.cs
+++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs
@@ -139,7 +139,7 @@ namespace NzbDrone.Core.Update
             // Set executable flag on update app
             if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
             {
-                _diskProvider.SetPermissions(_appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime), "0755");
+                _diskProvider.SetPermissions(_appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime), "0755", null);
             }
 
             _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime));
diff --git a/src/NzbDrone.Core/Validation/FileChmodValidator.cs b/src/NzbDrone.Core/Validation/FileChmodValidator.cs
index c9f0881a7..3e90bf9fa 100644
--- a/src/NzbDrone.Core/Validation/FileChmodValidator.cs
+++ b/src/NzbDrone.Core/Validation/FileChmodValidator.cs
@@ -3,11 +3,11 @@ using NzbDrone.Common.Disk;
 
 namespace NzbDrone.Core.Validation
 {
-    public class FileChmodValidator : PropertyValidator
+    public class FolderChmodValidator : PropertyValidator
     {
         private readonly IDiskProvider _diskProvider;
 
-        public FileChmodValidator(IDiskProvider diskProvider)
+        public FolderChmodValidator(IDiskProvider diskProvider)
             : base("Must contain a valid Unix permissions octal")
         {
             _diskProvider = diskProvider;
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Validation
                 return false;
             }
 
-            return _diskProvider.IsValidFilePermissionMask(context.PropertyValue.ToString());
+            return _diskProvider.IsValidFolderPermissionMask(context.PropertyValue.ToString());
         }
     }
 }
diff --git a/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs b/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs
index 19f008757..cb33e54f1 100644
--- a/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs
+++ b/src/NzbDrone.Mono.Test/DiskProviderTests/DiskProviderFixture.cs
@@ -170,15 +170,15 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
             Syscall.stat(tempFile, out var fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0444");
 
-            Subject.SetPermissions(tempFile, "644");
+            Subject.SetPermissions(tempFile, "755", null);
             Syscall.stat(tempFile, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0644");
 
-            Subject.SetPermissions(tempFile, "0644");
+            Subject.SetPermissions(tempFile, "0755", null);
             Syscall.stat(tempFile, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0644");
 
-            Subject.SetPermissions(tempFile, "1664");
+            Subject.SetPermissions(tempFile, "1775", null);
             Syscall.stat(tempFile, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("1664");
         }
@@ -195,56 +195,49 @@ namespace NzbDrone.Mono.Test.DiskProviderTests
             Syscall.stat(tempPath, out var fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0555");
 
-            Subject.SetPermissions(tempPath, "644");
+            Subject.SetPermissions(tempPath, "755", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0755");
 
-            Subject.SetPermissions(tempPath, "0644");
+            Subject.SetPermissions(tempPath, "0755", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0755");
 
-            Subject.SetPermissions(tempPath, "1664");
+            Subject.SetPermissions(tempPath, "1775", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("1775");
 
-            Subject.SetPermissions(tempPath, "775");
+            Subject.SetPermissions(tempPath, "775", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0775");
 
-            Subject.SetPermissions(tempPath, "640");
+            Subject.SetPermissions(tempPath, "750", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0750");
 
-            Subject.SetPermissions(tempPath, "0041");
+            Subject.SetPermissions(tempPath, "0051", null);
             Syscall.stat(tempPath, out fileStat);
             NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0051");
-
-            // reinstate sane permissions so fokder can be cleaned up
-            Subject.SetPermissions(tempPath, "775");
-            Syscall.stat(tempPath, out fileStat);
-            NativeConvert.ToOctalPermissionString(fileStat.st_mode).Should().Be("0775");
         }
 
         [Test]
-        public void IsValidFilePermissionMask_should_return_correct()
+        public void IsValidFolderPermissionMask_should_return_correct()
         {
-            // Files may not be executable
-            Subject.IsValidFilePermissionMask("0777").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0544").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0454").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0445").Should().BeFalse();
-
             // No special bits should be set
-            Subject.IsValidFilePermissionMask("1644").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("2644").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("4644").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("7644").Should().BeFalse();
-
-            // Files should be readable and writeable by owner
-            Subject.IsValidFilePermissionMask("0400").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0000").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0200").Should().BeFalse();
-            Subject.IsValidFilePermissionMask("0600").Should().BeTrue();
+            Subject.IsValidFolderPermissionMask("1755").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("2755").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("4755").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("7755").Should().BeFalse();
+
+            // Folder should be readable and writeable by owner
+            Subject.IsValidFolderPermissionMask("0000").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0100").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0200").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0300").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0400").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0500").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0600").Should().BeFalse();
+            Subject.IsValidFolderPermissionMask("0700").Should().BeTrue();
         }
     }
 }
diff --git a/src/NzbDrone.Mono/Disk/DiskProvider.cs b/src/NzbDrone.Mono/Disk/DiskProvider.cs
index 2172cbc01..ed5848309 100644
--- a/src/NzbDrone.Mono/Disk/DiskProvider.cs
+++ b/src/NzbDrone.Mono/Disk/DiskProvider.cs
@@ -78,50 +78,66 @@ namespace NzbDrone.Mono.Disk
         {
         }
 
-        public override void SetPermissions(string path, string mask)
+        public override void SetPermissions(string path, string mask, string group)
         {
             _logger.Debug("Setting permissions: {0} on {1}", mask, path);
 
             var permissions = NativeConvert.FromOctalPermissionString(mask);
 
-            if (_fileSystem.Directory.Exists(path))
+            if (_fileSystem.File.Exists(path))
             {
-                permissions = GetFolderPermissions(permissions);
+                permissions = GetFilePermissions(permissions);
             }
 
+            // Preserve non-access permissions
+            if (Syscall.stat(path, out var curStat) < 0)
+            {
+                var error = Stdlib.GetLastError();
+
+                throw new LinuxPermissionsException("Error getting current permissions: " + error);
+            }
+
+            permissions |= curStat.st_mode & ~FilePermissions.ACCESSPERMS;
+
             if (Syscall.chmod(path, permissions) < 0)
             {
                 var error = Stdlib.GetLastError();
 
                 throw new LinuxPermissionsException("Error setting permissions: " + error);
             }
+
+            var groupId = GetGroupId(group);
+
+            if (Syscall.chown(path, unchecked((uint)-1), groupId) < 0)
+            {
+                var error = Stdlib.GetLastError();
+
+                throw new LinuxPermissionsException("Error setting group: " + error);
+            }
         }
 
-        private static FilePermissions GetFolderPermissions(FilePermissions permissions)
+        private static FilePermissions GetFilePermissions(FilePermissions permissions)
         {
-            permissions |= (FilePermissions)((int)(permissions & (FilePermissions.S_IRUSR | FilePermissions.S_IRGRP | FilePermissions.S_IROTH)) >> 2);
+            permissions &= ~(FilePermissions.S_IXUSR | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH);
 
             return permissions;
         }
 
-        public override bool IsValidFilePermissionMask(string mask)
+        public override bool IsValidFolderPermissionMask(string mask)
         {
             try
             {
                 var permissions = NativeConvert.FromOctalPermissionString(mask);
 
-                if ((permissions & (FilePermissions.S_ISUID | FilePermissions.S_ISGID | FilePermissions.S_ISVTX)) != 0)
-                {
-                    return false;
-                }
-
-                if ((permissions & (FilePermissions.S_IXUSR | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH)) != 0)
+                if ((permissions & ~FilePermissions.ACCESSPERMS) != 0)
                 {
+                    // Only allow access permissions
                     return false;
                 }
 
-                if ((permissions & (FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) != (FilePermissions.S_IRUSR | FilePermissions.S_IWUSR))
+                if ((permissions & FilePermissions.S_IRWXU) != FilePermissions.S_IRWXU)
                 {
+                    // We expect at least full owner permissions (700)
                     return false;
                 }
 
diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
index 37908823f..45ae551ab 100644
--- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
+++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs
@@ -128,7 +128,7 @@ namespace NzbDrone.Update.UpdateEngine
                     // Set executable flag on Readarr app
                     if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
                     {
-                        _diskProvider.SetPermissions(Path.Combine(installationFolder, "Readarr"), "0755");
+                        _diskProvider.SetPermissions(Path.Combine(installationFolder, "Readarr"), "0755", null);
                     }
                 }
                 catch (Exception e)
diff --git a/src/NzbDrone.Windows/Disk/DiskProvider.cs b/src/NzbDrone.Windows/Disk/DiskProvider.cs
index 80dfaf0a8..d1fbfa100 100644
--- a/src/NzbDrone.Windows/Disk/DiskProvider.cs
+++ b/src/NzbDrone.Windows/Disk/DiskProvider.cs
@@ -102,7 +102,7 @@ namespace NzbDrone.Windows.Disk
             }
         }
 
-        public override void SetPermissions(string path, string mask)
+        public override void SetPermissions(string path, string mask, string group)
         {
         }
 
diff --git a/src/Readarr.Api.V1/Config/MediaManagementConfigModule.cs b/src/Readarr.Api.V1/Config/MediaManagementConfigModule.cs
index 3eaf411fd..fcc637946 100644
--- a/src/Readarr.Api.V1/Config/MediaManagementConfigModule.cs
+++ b/src/Readarr.Api.V1/Config/MediaManagementConfigModule.cs
@@ -8,11 +8,11 @@ namespace Readarr.Api.V1.Config
 {
     public class MediaManagementConfigModule : ReadarrConfigModule<MediaManagementConfigResource>
     {
-        public MediaManagementConfigModule(IConfigService configService, PathExistsValidator pathExistsValidator, FileChmodValidator fileChmodValidator)
+        public MediaManagementConfigModule(IConfigService configService, PathExistsValidator pathExistsValidator, FolderChmodValidator folderChmodValidator)
             : base(configService)
         {
             SharedValidator.RuleFor(c => c.RecycleBinCleanupDays).GreaterThanOrEqualTo(0);
-            SharedValidator.RuleFor(c => c.FileChmod).SetValidator(fileChmodValidator).When(c => !string.IsNullOrEmpty(c.FileChmod) && (OsInfo.IsLinux || OsInfo.IsOsx));
+            SharedValidator.RuleFor(c => c.ChmodFolder).SetValidator(folderChmodValidator).When(c => !string.IsNullOrEmpty(c.ChmodFolder) && (OsInfo.IsLinux || OsInfo.IsOsx));
             SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
             SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
         }
diff --git a/src/Readarr.Api.V1/Config/MediaManagementConfigResource.cs b/src/Readarr.Api.V1/Config/MediaManagementConfigResource.cs
index 66f359cbc..81e781be5 100644
--- a/src/Readarr.Api.V1/Config/MediaManagementConfigResource.cs
+++ b/src/Readarr.Api.V1/Config/MediaManagementConfigResource.cs
@@ -19,7 +19,8 @@ namespace Readarr.Api.V1.Config
         public AllowFingerprinting AllowFingerprinting { get; set; }
 
         public bool SetPermissionsLinux { get; set; }
-        public string FileChmod { get; set; }
+        public string ChmodFolder { get; set; }
+        public string ChownGroup { get; set; }
 
         public bool SkipFreeSpaceCheckWhenImporting { get; set; }
         public int MinimumFreeSpaceWhenImporting { get; set; }
@@ -46,7 +47,8 @@ namespace Readarr.Api.V1.Config
                 AllowFingerprinting = model.AllowFingerprinting,
 
                 SetPermissionsLinux = model.SetPermissionsLinux,
-                FileChmod = model.FileChmod,
+                ChmodFolder = model.ChmodFolder,
+                ChownGroup = model.ChownGroup,
 
                 SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
                 MinimumFreeSpaceWhenImporting = model.MinimumFreeSpaceWhenImporting,