Drupal downloads the module update but need FTP
I sometimes have seen that message with Drupal 7, and I always got confused about why Drupal was asking for FTP access to save files in the same server it was running, and why I didn't get that message in some Drupal 7 installations.
Looking at the code used from Drupal to updates the modules, I then discovered Drupal first checks if local file transfers are allowed. If they aren't allowed, that message will be shown.
In particular, this is the code Drupal uses. (See update_manager_local_transfers_allowed().)
  // Compare the owner of a webserver-created temporary file to the owner of
  // the configuration directory to determine if local transfers will be
  // allowed.
  $temporary_file = drupal_tempnam('temporary://', 'update_');
  $local_transfers_allowed = fileowner($temporary_file) === fileowner(conf_path());

In other words, if the account owning a temporary file created in the temporary directory set up for the site isn't the account owning the directory containing the settings.php file used for the site, Drupal will show the message you saw.
Setting the temporary directory and the directory containing the settings.php file to be owned from the same account will make updates from the user interface possible via local file transfers. In this case, Drupal won't show any message about the FTP access being required.
update_manager_local_transfers_allowed() is called from _update_manager_check_backends(), which contains the following code. (The comments explain what is happening and why.)
  // If file transfers will be performed locally, we do not need to display any
  // warnings or notices to the user and should automatically continue the
  // workflow, since we won't be using a FileTransfer backend that requires
  // user input or a specific server configuration.
  if (update_manager_local_transfers_allowed()) {
    return TRUE;
  }

  // Otherwise, show the available backends.
  $form['available_backends'] = array(
    '#prefix' =&gt; '&lt;p&gt;',
    '#suffix' =&gt; '&lt;/p&gt;',
  );
  $available_backends = drupal_get_filetransfer_info();
  if (empty($available_backends)) {
    if ($operation == 'update') {
      $form['available_backends']['#markup'] = t('Your server does not support updating modules and themes from this interface. Instead, update modules and themes by uploading the new versions directly to the server, as described in the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt;.', array(
        '@handbook_url' =&gt; 'http://drupal.org/getting-started/install-contrib',
      ));
    }
    else {
      $form['available_backends']['#markup'] = t('Your server does not support installing modules and themes from this interface. Instead, install modules and themes by uploading them directly to the server, as described in the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt;.', array(
        '@handbook_url' =&gt; 'http://drupal.org/getting-started/install-contrib',
      ));
    }
    return FALSE;
  }
  $backend_names = array();
  foreach ($available_backends as $backend) {
    $backend_names[] = $backend['title'];
  }
  if ($operation == 'update') {
    $form['available_backends']['#markup'] = format_plural(count($available_backends), 'Updating modules and themes requires &lt;strong&gt;@backends access&lt;/strong&gt; to your server. See the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt; for other update methods.', 'Updating modules and themes requires access to your server via one of the following methods: &lt;strong&gt;@backends&lt;/strong&gt;. See the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt; for other update methods.', array(
      '@backends' =&gt; implode(', ', $backend_names),
      '@handbook_url' =&gt; 'http://drupal.org/getting-started/install-contrib',
    ));
  }
  else {
    $form['available_backends']['#markup'] = format_plural(count($available_backends), 'Installing modules and themes requires &lt;strong&gt;@backends access&lt;/strong&gt; to your server. See the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt; for other installation methods.', 'Installing modules and themes requires access to your server via one of the following methods: &lt;strong&gt;@backends&lt;/strong&gt;. See the &lt;a href=&quot;@handbook_url&quot;&gt;handbook&lt;/a&gt; for other installation methods.', array(
      '@backends' =&gt; implode(', ', $backend_names),
      '@handbook_url' =&gt; 'http://drupal.org/getting-started/install-contrib',
    ));
  }
  return TRUE;

As side note, the temporary directory is the one set on admin/config/media/file-system, which by default is set to /tmp.



Note also that, as 4k4 said in a comment, updating modules or themes from the user interface is a security risk. In fact, it requires the system account used to run PHP to be able to write files in the directory containing the modules. For example, with that permission, a malicious script could alter the submission callback used for the user login form, and add code that sends the login credentials to a remote server.
For a general discussion see Should PHP run as www-data or as the user who owns the website? where one of the comments says:

PHP should NEVER have write access to or ownership of the files it executes, or the directories they reside in. I cannot emphasize this strongly enough. It just takes one security vulnerability, and now you're phishing, hosting a reverse shell, spamming, participating in an outbound DoS, or otherwise compromised, because an attacker used PHP to write a malicious script into a place where it could be executed. #3 is the best solution, best implemented by changing the group of the upload directory to www-data and giving group write access. Also ensure that nginx will not ask PHP to execute scripts stored in that directory.